feat(接口测试): 新增编辑器diff对比功能&接口测试对比布局调整&请求体JSON对比
This commit is contained in:
parent
d90d060e6e
commit
55a33c6a0b
|
@ -93,6 +93,8 @@
|
|||
const { t } = useI18n();
|
||||
// 编辑器实例,每次调用组件都会创建独立的实例
|
||||
let editor: monaco.editor.IStandaloneCodeEditor;
|
||||
// 编辑器diffEditor实例 开启diffMode则会初始化
|
||||
let diffEditor: monaco.editor.IStandaloneDiffEditor;
|
||||
const codeContainerRef = ref();
|
||||
|
||||
// 用于全屏的容器 ref
|
||||
|
@ -266,16 +268,17 @@
|
|||
codeheight.value = props.height;
|
||||
return;
|
||||
}
|
||||
const editorElement = editor.getDomNode();
|
||||
|
||||
if (!editorElement) {
|
||||
return;
|
||||
if (editor) {
|
||||
const editorElement = editor.getDomNode();
|
||||
if (!editorElement) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取代码编辑器文本行高
|
||||
const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight);
|
||||
const lineHeight = editor?.getOption(monaco.editor.EditorOption.lineHeight);
|
||||
// 获取代码的行数
|
||||
const lineCount = editor.getModel()?.getLineCount() || 10;
|
||||
const lineCount = editor?.getModel()?.getLineCount() || 10;
|
||||
// 计算高度 @desc 原本行数差3行完全展示文本 24为上下的边距为12px
|
||||
const height = (lineCount + 3) * lineHeight;
|
||||
codeheight.value = height > 300 ? `${height + 24}px` : '300px';
|
||||
|
@ -343,11 +346,50 @@
|
|||
{ deep: true }
|
||||
);
|
||||
|
||||
// 初始化diffEditor
|
||||
const initDiffEditor = (originalValue: string, modifiedValue: string) => {
|
||||
diffEditor = monaco.editor.createDiffEditor(codeContainerRef.value, {
|
||||
automaticLayout: true,
|
||||
padding: {
|
||||
top: 12,
|
||||
bottom: 12,
|
||||
},
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
contextmenu: !props.readOnly,
|
||||
...props,
|
||||
theme: currentTheme.value,
|
||||
lineNumbersMinChars: 3,
|
||||
lineDecorationsWidth: 0,
|
||||
scrollBeyondLastLine: false,
|
||||
});
|
||||
|
||||
const originalModel = monaco.editor.createModel(originalValue, props.language.toLowerCase());
|
||||
const modifiedModel = monaco.editor.createModel(modifiedValue, props.language.toLowerCase());
|
||||
|
||||
diffEditor.setModel({
|
||||
original: originalModel,
|
||||
modified: modifiedModel,
|
||||
});
|
||||
|
||||
handleEditorMount();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.language,
|
||||
(newValue) => {
|
||||
currentLanguage.value = newValue;
|
||||
monaco.editor.setModelLanguage(editor.getModel()!, newValue.toLowerCase()); // 设置语言,语言 ENUM 是大写的,但是 monaco 需要小写
|
||||
// 设置对比初始和修改值
|
||||
if (diffEditor) {
|
||||
const originalModel = diffEditor.getModel()?.original;
|
||||
const modifiedModel = diffEditor.getModel()?.modified;
|
||||
if (originalModel && modifiedModel) {
|
||||
monaco.editor.setModelLanguage(originalModel, newValue.toLowerCase());
|
||||
monaco.editor.setModelLanguage(modifiedModel, newValue.toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -355,15 +397,28 @@
|
|||
() => props.readOnly,
|
||||
(val) => {
|
||||
editor.updateOptions({ readOnly: val });
|
||||
if (diffEditor) {
|
||||
diffEditor.updateOptions({ readOnly: val });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
editor.dispose();
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
}
|
||||
|
||||
if (diffEditor) {
|
||||
diffEditor.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
if (props.diffMode) {
|
||||
initDiffEditor(props.originalValue, props.modelValue);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
setEditBoxBg();
|
||||
if (props.readOnly) {
|
||||
format();
|
||||
|
|
|
@ -135,4 +135,14 @@ export const editorProps = {
|
|||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
// diff对比模式
|
||||
diffMode: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
// 原来值
|
||||
originalValue: {
|
||||
type: String as PropType<string>,
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
</div>
|
||||
<div v-if="showDiff(RequestComposition.REST)" class="title">REST</div>
|
||||
<div
|
||||
v-if="showDiff(RequestComposition.REST)"
|
||||
v-if="showDiff(RequestComposition.REST) && hiddenEmptyTable(RequestComposition.REST)"
|
||||
:style="{ 'padding-bottom': `${getBottomDistance(RequestComposition.REST)}px` }"
|
||||
>
|
||||
<MsFormTable
|
||||
|
@ -56,20 +56,11 @@
|
|||
>
|
||||
{{ 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="
|
||||
|
@ -96,52 +87,13 @@
|
|||
>
|
||||
{{ 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';
|
||||
|
||||
|
@ -150,7 +102,6 @@
|
|||
|
||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
|
||||
const { copy, isSupported } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -259,8 +210,6 @@
|
|||
},
|
||||
];
|
||||
|
||||
const bodyShowType = ref('schema');
|
||||
|
||||
/**
|
||||
* 请求体
|
||||
*/
|
||||
|
@ -376,42 +325,6 @@
|
|||
}
|
||||
});
|
||||
|
||||
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'));
|
||||
|
||||
// 设置非空间距确保行内容长度不同能够水平看齐对比
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
<template>
|
||||
<template
|
||||
v-if="
|
||||
[RequestBodyFormat.JSON, RequestBodyFormat.RAW, RequestBodyFormat.XML].includes(
|
||||
previewDefinedDetail?.body?.bodyType
|
||||
)
|
||||
"
|
||||
>
|
||||
<MsCodeEditor
|
||||
v-if="previewDefinedDetail?.body?.bodyType === RequestBodyFormat.JSON"
|
||||
:model-value="bodyCaseCode"
|
||||
theme="vs"
|
||||
height="200px"
|
||||
:language="bodyCodeLanguage"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
read-only
|
||||
is-adaptive
|
||||
diff-mode
|
||||
:original-value="bodyDefinedCode"
|
||||
>
|
||||
<template #rightTitle>
|
||||
<a-button
|
||||
type="outline"
|
||||
class="arco-btn-outline--secondary p-[0_8px]"
|
||||
size="mini"
|
||||
@click="copyScript(bodyDefinedCode)"
|
||||
>
|
||||
<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 { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ExecuteBody } from '@/models/apiTest/common';
|
||||
import { RequestBodyFormat } from '@/enums/apiEnum';
|
||||
|
||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
|
||||
const MsCodeEditor = defineAsyncComponent(() => import('@/components/pure/ms-code-editor/index.vue'));
|
||||
|
||||
const { copy, isSupported } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
definedDetail: RequestParam;
|
||||
caseDetail: RequestParam;
|
||||
}>();
|
||||
|
||||
const previewDefinedDetail = ref<RequestParam>(props.definedDetail);
|
||||
const previewCaseDetail = ref<RequestParam>(props.caseDetail);
|
||||
|
||||
const bodyCodeLanguage = computed(() => {
|
||||
if (previewDefinedDetail.value?.body?.bodyType === RequestBodyFormat.JSON) {
|
||||
return LanguageEnum.JSON;
|
||||
}
|
||||
if (previewDefinedDetail.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 getBodyCode = (body: ExecuteBody) => {
|
||||
switch (body?.bodyType) {
|
||||
case RequestBodyFormat.FORM_DATA:
|
||||
return body.formDataBody?.formValues?.map((item: any) => `${item.key}:${item.value}`).join('\n');
|
||||
case RequestBodyFormat.WWW_FORM:
|
||||
return body.wwwFormBody?.formValues?.map((item: any) => `${item.key}:${item.value}`).join('\n');
|
||||
case RequestBodyFormat.RAW:
|
||||
return body.rawBody?.value;
|
||||
case RequestBodyFormat.JSON:
|
||||
return body.jsonBody?.jsonValue;
|
||||
case RequestBodyFormat.XML:
|
||||
return body.xmlBody?.value;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// 接口定义Code
|
||||
const bodyDefinedCode = computed(() => getBodyCode(previewDefinedDetail.value?.body));
|
||||
|
||||
// 用例Code
|
||||
const bodyCaseCode = computed(() => getBodyCode(previewCaseDetail.value?.body));
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.definedDetail) {
|
||||
previewDefinedDetail.value = cloneDeep(props.definedDetail);
|
||||
}
|
||||
if (props.caseDetail) {
|
||||
previewCaseDetail.value = cloneDeep(props.caseDetail);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -67,14 +67,27 @@
|
|||
</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>
|
||||
<a-spin class="h-full w-full" :loading="loading">
|
||||
<div class="diff-normal">
|
||||
<div class="diff-item">
|
||||
<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-[24px]">
|
||||
<div class="title-type"> [{{ caseDetail?.num }}] {{ caseDetail?.name }} </div>
|
||||
<DiffItem :diff-distance-map="diffDistanceMap" mode="delete" :detail="caseDetail as RequestParam" />
|
||||
</div>
|
||||
</div>
|
||||
<DiffRequestBody
|
||||
:defined-detail="apiDefinedRequest as RequestParam"
|
||||
:case-detail="caseDetail as RequestParam"
|
||||
/>
|
||||
</a-spin>
|
||||
</div>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
@ -86,6 +99,7 @@
|
|||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||
import DiffItem from './diffItem.vue';
|
||||
import DiffRequestBody from './diffRequestBody.vue';
|
||||
|
||||
import { getCaseDetail, getDefinitionDetail } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -323,13 +337,16 @@
|
|||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
async function getRequestDetail(definedId: string, apiCaseId: string) {
|
||||
loading.value = true;
|
||||
try {
|
||||
await Promise.all([getApiDetail(definedId), getCaseDetailInfo(apiCaseId)]);
|
||||
processData();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -361,31 +378,33 @@
|
|||
}
|
||||
}
|
||||
.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;
|
||||
|
||||
padding: 16px;
|
||||
min-height: calc(100vh - 110px);
|
||||
border-radius: 12px;
|
||||
background: white;
|
||||
box-shadow: 0 0 10px rgba(120 56 135/ 5%);
|
||||
@apply mx-4;
|
||||
.diff-normal {
|
||||
@apply flex;
|
||||
.diff-item {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue