feat(json-schema): ms-json-schema组件Done

This commit is contained in:
baiqi 2024-07-04 15:42:32 +08:00 committed by 刘瑞斌
parent 9b97fe0664
commit 37a7993785
15 changed files with 483 additions and 165 deletions

View File

@ -72,7 +72,7 @@
<style lang="less"> <style lang="less">
/** 面包屑 **/ /** 面包屑 **/
.arco-breadcrumb-item { .arco-breadcrumb-item {
@apply cursor-pointer; @apply cursor-pointer break-keep;
color: var(--color-text-4); color: var(--color-text-4);
&:hover { &:hover {

View File

@ -527,7 +527,7 @@
if (node.children && node.children.length > 0) { if (node.children && node.children.length > 0) {
waitingRenderNodes = waitingRenderNodes.concat(node.children); waitingRenderNodes = waitingRenderNodes.concat(node.children);
} }
if (total > list.length * current) { if (total > 100 * (current + 1)) {
// //
const moreNode = window.minder.createNode( const moreNode = window.minder.createNode(
{ {

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <div class="flex w-full">
<a-popover position="tl" :disabled="!modelValue || modelValue.trim() === ''" class="ms-params-input-popover"> <a-popover position="tl" :disabled="!modelValue || modelValue.trim() === ''" class="ms-params-input-popover">
<template #content> <template #content>
<div v-if="props.title" class="ms-params-popover-title"> <div v-if="props.title" class="ms-params-popover-title">
@ -38,26 +38,26 @@
@change="(val) => emit('change', val)" @change="(val) => emit('change', val)"
/> />
</a-popover> </a-popover>
<a-modal
v-model:visible="showQuickInput"
:title="props.title"
:ok-text="t('common.save')"
:ok-button-props="{ disabled: !quickInputValue || quickInputValue.trim() === '' }"
class="ms-modal-form"
body-class="!p-0"
:width="480"
title-align="start"
@ok="applyQuickInputDesc"
@close="clearQuickInputDesc"
>
<a-textarea
v-model:model-value="quickInputValue"
:placeholder="props.placeholder"
:auto-size="{ minRows: 2 }"
:max-length="1000"
/>
</a-modal>
</div> </div>
<a-modal
v-model:visible="showQuickInput"
:title="props.title"
:ok-text="t('common.save')"
:ok-button-props="{ disabled: !quickInputValue || quickInputValue.trim() === '' }"
class="ms-modal-form"
body-class="!p-0"
:width="480"
title-align="start"
@ok="applyQuickInputDesc"
@close="clearQuickInputDesc"
>
<a-textarea
v-model:model-value="quickInputValue"
:placeholder="props.placeholder"
:auto-size="{ minRows: 2 }"
:max-length="1000"
/>
</a-modal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -461,8 +461,14 @@
</template> </template>
</MsCodeEditor> </MsCodeEditor>
</MsDrawer> </MsDrawer>
<MsDrawer v-model:visible="previewDrawerVisible" :width="600" :title="t('common.preview')" :footer="false"> <MsDrawer
<a-spin class="block" :loading="previewDrawerLoading"> v-model:visible="previewDrawerVisible"
:width="600"
:title="t('common.preview')"
:footer="false"
@close="previewShowType = 'json'"
>
<a-spin class="block h-full w-full" :loading="previewDrawerLoading">
<MsCodeEditor <MsCodeEditor
v-model:model-value="activePreviewValue" v-model:model-value="activePreviewValue"
theme="vs" theme="vs"
@ -500,8 +506,12 @@
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { JsonSchema, JsonSchemaTableItem } from './types'; import { JsonSchema, JsonSchemaItem, JsonSchemaTableItem } from './types';
import { parseJsonToJsonSchemaTableItem, tableItemToJsonSchema } from './utils'; import {
parseJsonToJsonSchemaTableData,
parseSchemaToJsonSchemaTableData,
parseTableDataToJsonSchema,
} from './utils';
const { t } = useI18n(); const { t } = useI18n();
@ -523,29 +533,43 @@
children: undefined, children: undefined,
}; };
const data = defineModel<JsonSchemaTableItem[]>('data', { const data = defineModel<JsonSchemaTableItem[]>('data', {
default: () => [ default: () => [],
{ });
id: 'root', const expandKeys = defineModel<string[]>('expandKeys', {
title: 'root', default: () => ['root'],
type: 'object', });
example: '', const selectedKeys = defineModel<string[]>('selectedKeys', {
description: '', default: () => ['root'],
enable: true, });
defaultValue: '',
maximum: undefined, //
minimum: undefined, watchEffect(() => {
maxLength: undefined, if (data.value.length === 0) {
minLength: undefined, data.value = [
enumValues: '', {
pattern: undefined, id: 'root',
format: undefined, title: 'root',
required: false, type: 'object',
children: [], example: '',
}, description: '',
], enable: true,
defaultValue: '',
maximum: undefined,
minimum: undefined,
maxLength: undefined,
minLength: undefined,
enumValues: '',
pattern: undefined,
format: undefined,
required: false,
children: [],
},
];
if (selectedKeys.value.length === 0) {
selectedKeys.value = ['root'];
}
}
}); });
const expandKeys = ref<string[]>(['root']);
const selectedKeys = ref<string[]>(['root']);
const typeOptions: SelectOptionData[] = [ const typeOptions: SelectOptionData[] = [
{ {
@ -845,14 +869,22 @@
} }
function applyBatchAdd() { function applyBatchAdd() {
if (batchAddType.value === 'json') { try {
const res = parseJsonToJsonSchemaTableItem(batchAddValue.value); let res: { result: JsonSchemaTableItem[]; ids: Array<string> } = { result: [], ids: [] };
if (batchAddType.value === 'json') {
res = parseJsonToJsonSchemaTableData(batchAddValue.value);
} else {
res = parseSchemaToJsonSchemaTableData(batchAddValue.value);
}
if (res.result.length > 0) { if (res.result.length > 0) {
data.value = res.result; data.value = res.result;
selectedKeys.value = res.ids; selectedKeys.value = res.ids;
} }
batchAddDrawerVisible.value = false;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} }
batchAddDrawerVisible.value = false;
} }
const settingDrawerVisible = ref(false); const settingDrawerVisible = ref(false);
@ -870,7 +902,7 @@
...record, ...record,
}; };
try { try {
const schema = tableItemToJsonSchema(record, record.id === 'root'); const schema = parseTableDataToJsonSchema(record, record.id === 'root');
activePreviewJsonSchemaValue.value = JSON.stringify(schema); activePreviewJsonSchemaValue.value = JSON.stringify(schema);
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -883,7 +915,7 @@
function handleSettingFormChange() { function handleSettingFormChange() {
activePreviewJsonSchemaValue.value = JSON.stringify( activePreviewJsonSchemaValue.value = JSON.stringify(
tableItemToJsonSchema(activeRecord.value, activeRecord.value.id === 'root') parseTableDataToJsonSchema(activeRecord.value, activeRecord.value.id === 'root')
); );
} }
@ -927,19 +959,32 @@
const previewDrawerVisible = ref(false); const previewDrawerVisible = ref(false);
const previewDrawerLoading = ref(false); const previewDrawerLoading = ref(false);
/**
* 预览 schema
*/
async function previewSchema() { async function previewSchema() {
previewDrawerVisible.value = true; previewDrawerVisible.value = true;
previewDrawerLoading.value = true;
let schema: JsonSchema | JsonSchemaItem | undefined;
try { try {
previewDrawerLoading.value = true; // json schema
const schema = tableItemToJsonSchema(data.value[0]); schema = parseTableDataToJsonSchema(data.value[0] as JsonSchemaTableItem);
const res = await convertJsonSchemaToJson(schema as JsonSchema);
activePreviewJsonValue.value = JSON.stringify(res);
activePreviewJsonSchemaValue.value = JSON.stringify(schema); activePreviewJsonSchemaValue.value = JSON.stringify(schema);
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
activePreviewJsonValue.value = t('ms.json.schema.convertFailed');
activePreviewJsonSchemaValue.value = t('ms.json.schema.convertFailed'); activePreviewJsonSchemaValue.value = t('ms.json.schema.convertFailed');
previewDrawerLoading.value = false;
return;
}
try {
// json schema json
const res = await convertJsonSchemaToJson(schema as JsonSchema);
activePreviewJsonValue.value = JSON.stringify(res);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
activePreviewJsonValue.value = t('ms.json.schema.convertFailed');
} finally { } finally {
previewDrawerLoading.value = false; previewDrawerLoading.value = false;
} }

View File

@ -0,0 +1,25 @@
export default {
'ms.json.schema.name': 'Parameter Name',
'ms.json.schema.nameNotNull': 'Parameter Name cannot be empty',
'ms.json.schema.type': 'Type',
'ms.json.schema.value': 'Parameter Value',
'ms.json.schema.addChild': 'Add Child Field',
'ms.json.schema.advancedSettings': 'Advanced Settings',
'ms.json.schema.minLength': 'Minimum Length',
'ms.json.schema.maxLength': 'Maximum Length',
'ms.json.schema.minimum': 'Minimum Value',
'ms.json.schema.maximum': 'Maximum Value',
'ms.json.schema.default': 'Default Value',
'ms.json.schema.enum': 'Enumeration',
'ms.json.schema.enumPlaceholder': '1 line, 1 enumeration value',
'ms.json.schema.regex': 'Regular Expression',
'ms.json.schema.regexPlaceholder': 'e.g. {reg}',
'ms.json.schema.format': 'Format',
'ms.json.schema.preview': 'Preview',
'ms.json.schema.batchAdd': 'Batch Add',
'ms.json.schema.batchAddTip': 'Write in the format: "key":"value", e.g. "name":"natural"',
'ms.json.schema.convertFailed': 'Data conversion failed, please try again',
'ms.json.schema.minItems': 'Minimum number of items',
'ms.json.schema.maxItems': 'Maximum number of items',
'ms.json.schema.illegalJsonConvertFailed': 'Conversion failed, please check if the input JSON structure is valid',
};

View File

@ -2,7 +2,7 @@ export interface JsonSchemaCommon {
id: string; id: string;
title: string; // 参数名称 title: string; // 参数名称
type: string; // 参数类型 type: string; // 参数类型
description: string; // 参数描述 description?: string; // 参数描述
enable: boolean; // 是否启用 enable: boolean; // 是否启用
example: string; // 参数值 example: string; // 参数值
defaultValue: string | number | boolean; // 默认值 defaultValue: string | number | boolean; // 默认值
@ -35,4 +35,5 @@ export interface JsonSchema {
properties?: Record<string, JsonSchemaItem>; properties?: Record<string, JsonSchemaItem>;
items?: JsonSchemaItem[]; items?: JsonSchemaItem[];
required?: string[]; required?: string[];
description?: string;
} }

View File

@ -10,61 +10,70 @@ import type { JsonSchema, JsonSchemaItem, JsonSchemaTableItem } from './types';
* @param schemaItem * @param schemaItem
* @param isRoot * @param isRoot
*/ */
export function tableItemToJsonSchema( export function parseTableDataToJsonSchema(
schemaItem: JsonSchemaTableItem, schemaItem: JsonSchemaTableItem,
isRoot: boolean = true isRoot: boolean = true
): JsonSchema | JsonSchemaItem { ): JsonSchema | JsonSchemaItem | undefined {
let schema: JsonSchema | JsonSchemaItem = { type: schemaItem.type }; try {
let schema: JsonSchema | JsonSchemaItem = { type: schemaItem.type };
// 对于 null 类型,只设置 type 和 enable 属性 // 对于 null 类型,只设置 type 和 enable 属性
if (schemaItem.type === 'null') { if (schemaItem.type === 'null') {
return { return {
type: 'null', type: 'null',
enable: schemaItem.enable,
};
}
if (!isRoot) {
// 使用解构赋值和剩余参数来拷贝对象,同时排除 children、required、parent、id、title 属性
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { children, required, parent, id, title, ...copiedObject } = schemaItem;
// 使用\n分割enumValues字符串得到枚举值数组
const enumArray = (copiedObject.enumValues?.split('\n') || []).filter((value) => value.trim() !== '');
schema = {
...schema,
...copiedObject,
enumValues: enumArray.length > 0 ? enumArray : undefined,
};
}
if (schemaItem.children && schemaItem.children.length > 0) {
if (schemaItem.type === 'object') {
schema = {
type: 'object',
enable: schemaItem.enable, enable: schemaItem.enable,
properties: {},
required: [],
};
schemaItem.children.forEach((child) => {
const childSchema = tableItemToJsonSchema(child, false);
schema.properties![child.title] = childSchema as JsonSchemaItem;
if (child.required) {
schema.required!.push(child.title);
}
});
if (schema.required!.length === 0) {
delete schema.required;
}
} else if (schemaItem.type === 'array') {
schema = {
type: 'array',
enable: schemaItem.enable,
items: schemaItem.children.map((child) => tableItemToJsonSchema(child, false) as JsonSchemaItem),
}; };
} }
}
return schema; if (!isRoot) {
// 非根节点,组装普通节点信息
// 使用解构赋值和剩余参数来拷贝对象,同时排除 children、required、parent、id、title 属性
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { children, required, parent, id, title, ...copiedObject } = schemaItem;
// 使用\n分割enumValues字符串得到枚举值数组
const enumArray = (copiedObject.enumValues?.split('\n') || []).filter((value) => value.trim() !== '');
schema = {
...schema,
...copiedObject,
enumValues: enumArray.length > 0 ? enumArray : undefined,
};
}
if (schemaItem.children && schemaItem.children.length > 0) {
if (schemaItem.type === 'object') {
// 对象类型
schema = {
type: 'object',
enable: schemaItem.enable,
properties: {},
required: [],
};
schemaItem.children.forEach((child) => {
const childSchema = parseTableDataToJsonSchema(child, false);
schema.properties![child.title] = childSchema as JsonSchemaItem;
if (child.required) {
schema.required!.push(child.title);
}
});
if (schema.required!.length === 0) {
delete schema.required;
}
} else if (schemaItem.type === 'array') {
// 数组类型
schema = {
type: 'array',
enable: schemaItem.enable,
items: schemaItem.children.map((child) => parseTableDataToJsonSchema(child, false) as JsonSchemaItem),
};
}
}
return schema;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
return undefined;
}
} }
/** /**
@ -94,12 +103,13 @@ function createItem(key: string, value: any, parent?: JsonSchemaTableItem): Json
parent, parent,
}; };
} }
/** /**
* json json-schema * json json-schema
* @param json json * @param json json
* @param parent * @param parent
*/ */
export function parseJsonToJsonSchemaTableItem( export function parseJsonToJsonSchemaTableData(
json: string | object | Array<any>, json: string | object | Array<any>,
parent?: JsonSchemaTableItem parent?: JsonSchemaTableItem
): { result: JsonSchemaTableItem[]; ids: Array<string> } { ): { result: JsonSchemaTableItem[]; ids: Array<string> } {
@ -125,7 +135,7 @@ export function parseJsonToJsonSchemaTableItem(
example: '', example: '',
defaultValue: '', defaultValue: '',
}; };
const children = parseJsonToJsonSchemaTableItem(json, rootItem); const children = parseJsonToJsonSchemaTableData(json, rootItem);
rootItem.children = children.result; rootItem.children = children.result;
children.ids.push(rootItem.id); children.ids.push(rootItem.id);
return { result: [rootItem], ids: children.ids }; return { result: [rootItem], ids: children.ids };
@ -140,7 +150,7 @@ export function parseJsonToJsonSchemaTableItem(
Object.entries(json).forEach(([key, value]) => { Object.entries(json).forEach(([key, value]) => {
const item: JsonSchemaTableItem = createItem(key, value, parent); const item: JsonSchemaTableItem = createItem(key, value, parent);
if (typeof value === 'object' || Array.isArray(value)) { if (typeof value === 'object' || Array.isArray(value)) {
const children = parseJsonToJsonSchemaTableItem(value, item); const children = parseJsonToJsonSchemaTableData(value, item);
item.children = children.result; item.children = children.result;
ids.push(...children.ids); ids.push(...children.ids);
} else { } else {
@ -153,3 +163,76 @@ export function parseJsonToJsonSchemaTableItem(
return { result: items, ids }; return { result: items, ids };
} }
/**
* json-schema json-schema
* @param schema json-schema /
*/
export function parseSchemaToJsonSchemaTableData(schema: string | JsonSchema): {
result: JsonSchemaTableItem[];
ids: Array<string>;
} {
const ids: Array<string> = ['root'];
if (typeof schema === 'string') {
// 尝试将 json 字符串转换为对象
try {
schema = JSON.parse(schema);
} catch (error) {
const { t } = useI18n();
Message.warning(t('ms.json.schema.illegalJsonConvertFailed'));
return { result: [], ids: [] };
}
}
const parseNode = (
node: JsonSchema | JsonSchemaItem,
parent?: JsonSchemaTableItem,
requiredFields?: string[]
): JsonSchemaTableItem => {
let item: JsonSchemaTableItem;
if (!parent) {
// 根节点
item = {
id: 'root',
title: 'root',
type: node.type,
description: node.description,
enable: true,
required: true,
example: '',
defaultValue: '',
};
} else {
// 子孙节点
// 剔除不需要的属性 properties、items
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { properties, items, ...copiedObj } = node;
const itemId = getGenerateId();
item = {
...(copiedObj as JsonSchemaItem),
id: itemId,
required: requiredFields?.includes((node as JsonSchemaItem).title),
enumValues: (node as JsonSchemaItem).enumValues?.join('\n'),
parent,
};
if ((node as JsonSchemaItem).enable === true || (node as JsonSchemaItem).enable === undefined) {
// 如果enable为true或者undefined则设置为选中
ids.push(itemId);
}
}
// 检查当前节点的required属性是否存在且为数组类型然后传递当前节点的required属性给子节点判断是否必填
const newRequiredFields = Array.isArray(node.required) ? node.required : undefined;
if ((node.type === 'object' && node.properties) || (node.type === 'array' && node.items)) {
const children = node.type === 'object' ? node.properties : node.items;
item.children = Object.entries(children || []).map(([key, childNode]) =>
parseNode({ ...childNode, title: key }, item, newRequiredFields)
);
}
return item;
};
const result = [parseNode(schema as JsonSchema)];
return { result, ids };
}

View File

@ -1,5 +1,5 @@
import { Language } from '@/components/pure/ms-code-editor/types'; import { Language } from '@/components/pure/ms-code-editor/types';
import type { JsonSchemaItem } from '@/components/pure/ms-json-schema/types'; import type { JsonSchema, JsonSchemaTableItem } from '@/components/pure/ms-json-schema/types';
import { import {
type FullResponseAssertionType, type FullResponseAssertionType,
@ -124,8 +124,10 @@ export interface ExecuteBinaryBody {
export interface ExecuteJsonBody { export interface ExecuteJsonBody {
enableJsonSchema?: boolean; enableJsonSchema?: boolean;
enableTransition?: boolean; enableTransition?: boolean;
jsonSchema?: JsonSchemaItem[]; jsonSchema?: JsonSchema;
jsonValue: string; jsonValue: string;
// 前端渲染字段
jsonSchemaTableData?: JsonSchemaTableItem[];
} }
// 执行请求配置 // 执行请求配置
export interface ExecuteOtherConfig { export interface ExecuteOtherConfig {

View File

@ -89,8 +89,9 @@ export const defaultResponseItem: ResponseDefinition = {
bodyType: ResponseBodyFormat.JSON, bodyType: ResponseBodyFormat.JSON,
jsonBody: { jsonBody: {
jsonValue: '', jsonValue: '',
enableJsonSchema: false, enableJsonSchema: true,
enableTransition: false, enableTransition: false,
jsonSchemaTableData: [],
}, },
xmlBody: { xmlBody: {
value: '', value: '',
@ -117,6 +118,8 @@ export const defaultBodyParams: ExecuteBody = {
}, },
jsonBody: { jsonBody: {
jsonValue: '', jsonValue: '',
enableJsonSchema: true,
jsonSchemaTableData: [],
}, },
xmlBody: { value: '' }, xmlBody: { value: '' },
rawBody: { value: '' }, rawBody: { value: '' },
@ -312,6 +315,8 @@ export const mockDefaultParams: MockParams = {
}, },
jsonBody: { jsonBody: {
jsonValue: '', jsonValue: '',
enableJsonSchema: true,
jsonSchemaTableData: [],
}, },
xmlBody: { value: '' }, xmlBody: { value: '' },
rawBody: { value: '' }, rawBody: { value: '' },
@ -331,8 +336,9 @@ export const mockDefaultParams: MockParams = {
bodyType: ResponseBodyFormat.JSON, bodyType: ResponseBodyFormat.JSON,
jsonBody: { jsonBody: {
jsonValue: '', jsonValue: '',
enableJsonSchema: false, enableJsonSchema: true,
enableTransition: false, enableTransition: false,
jsonSchemaTableData: [],
}, },
xmlBody: { xmlBody: {
value: '', value: '',

View File

@ -85,27 +85,35 @@
</a-tooltip> </a-tooltip>
</div> --> </div> -->
</div> </div>
<div v-else class="h-[calc(100%-34px)]"> <a-spin v-else :loading="bodyLoading" class="block h-[calc(100%-34px)]">
<div class="mb-[8px] flex items-center justify-between"> <div class="mb-[8px] flex items-center justify-between">
<div class="flex items-center gap-[8px]"> <div class="flex items-center gap-[8px]">
<MsButton <MsButton
type="text" type="text"
class="!mr-0" class="!mr-0"
:class="jsonType === 'Schema' ? 'font-medium !text-[rgb(var(--primary-5))]' : '!text-[var(--color-text-4)]'" :class="
@click="jsonType = 'Schema'" innerParams.jsonBody.enableJsonSchema
? 'font-medium !text-[rgb(var(--primary-5))]'
: '!text-[var(--color-text-4)]'
"
@click="innerParams.jsonBody.enableJsonSchema = true"
>Schema</MsButton >Schema</MsButton
> >
<a-divider :margin="0" direction="vertical"></a-divider> <a-divider :margin="0" direction="vertical"></a-divider>
<MsButton <MsButton
type="text" type="text"
class="!mr-0" class="!mr-0"
:class="jsonType === 'Json' ? 'font-medium !text-[rgb(var(--primary-5))]' : '!text-[var(--color-text-4)]'" :class="
@click="jsonType = 'Json'" !innerParams.jsonBody.enableJsonSchema
? 'font-medium !text-[rgb(var(--primary-5))]'
: '!text-[var(--color-text-4)]'
"
@click="innerParams.jsonBody.enableJsonSchema = false"
>Json</MsButton >Json</MsButton
> >
</div> </div>
<a-button <a-button
v-show="jsonType === 'Schema'" v-show="innerParams.jsonBody.enableJsonSchema"
type="outline" type="outline"
class="arco-btn-outline--secondary px-[8px]" class="arco-btn-outline--secondary px-[8px]"
size="small" size="small"
@ -117,8 +125,14 @@
</div> </div>
</a-button> </a-button>
</div> </div>
<MsJsonSchema
v-if="innerParams.jsonBody.enableJsonSchema"
ref="jsonSchemaRef"
v-model:data="innerParams.jsonBody.jsonSchemaTableData"
v-model:selectedKeys="selectedKeys"
/>
<MsCodeEditor <MsCodeEditor
v-if="jsonType === 'Json'" v-else
v-model:model-value="currentBodyCode" v-model:model-value="currentBodyCode"
:read-only="props.disabledExceptParam" :read-only="props.disabledExceptParam"
theme="vs" theme="vs"
@ -129,9 +143,13 @@
:language="currentCodeLanguage" :language="currentCodeLanguage"
is-adaptive is-adaptive
> >
<template #rightTitle>
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="autoMakeJson">
<div class="text-[var(--color-text-1)]">{{ t('apiTestManagement.autoMake') }}</div>
</a-button>
</template>
</MsCodeEditor> </MsCodeEditor>
<MsJsonSchema v-else ref="jsonSchemaRef" /> </a-spin>
</div>
<batchAddKeyVal <batchAddKeyVal
v-if="showParamTable" v-if="showParamTable"
v-model:visible="batchAddKeyValVisible" v-model:visible="batchAddKeyValVisible"
@ -143,17 +161,19 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { TableColumnData } from '@arco-design/web-vue'; import { Message, TableColumnData } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue'; import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import { LanguageEnum } from '@/components/pure/ms-code-editor/types'; import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
import MsJsonSchema from '@/components/pure/ms-json-schema/index.vue'; import MsJsonSchema from '@/components/pure/ms-json-schema/index.vue';
import { parseSchemaToJsonSchemaTableData, parseTableDataToJsonSchema } from '@/components/pure/ms-json-schema/utils';
import { MsFileItem } from '@/components/pure/ms-upload/types'; import { MsFileItem } from '@/components/pure/ms-upload/types';
import MsAddAttachment from '@/components/business/ms-add-attachment/index.vue'; import MsAddAttachment from '@/components/business/ms-add-attachment/index.vue';
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue'; import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
import paramTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue'; import paramTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import { convertJsonSchemaToJson } from '@/api/modules/api-test/management';
import { requestBodyTypeMap } from '@/config/apiTest'; import { requestBodyTypeMap } from '@/config/apiTest';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
@ -186,6 +206,9 @@
const innerParams = defineModel<ExecuteBody>('params', { const innerParams = defineModel<ExecuteBody>('params', {
required: true, required: true,
}); });
const selectedKeys = ref<string[]>([]);
const bodyLoading = ref(false);
const batchAddKeyValVisible = ref(false); const batchAddKeyValVisible = ref(false);
const fileList = ref<MsFileItem[]>([]); const fileList = ref<MsFileItem[]>([]);
@ -202,6 +225,17 @@
} }
); );
watchEffect(() => {
if (innerParams.value.jsonBody.jsonSchema) {
const { result, ids } = parseSchemaToJsonSchemaTableData(innerParams.value.jsonBody.jsonSchema);
innerParams.value.jsonBody.jsonSchemaTableData = result;
selectedKeys.value = ids;
} else {
innerParams.value.jsonBody.jsonSchemaTableData = [];
selectedKeys.value = [];
}
});
async function handleFileChange(files: MsFileItem[], file?: MsFileItem) { async function handleFileChange(files: MsFileItem[], file?: MsFileItem) {
try { try {
if (file?.local && file.file && props.uploadTempFileApi) { if (file?.local && file.file && props.uploadTempFileApi) {
@ -320,15 +354,42 @@
}, },
}); });
const jsonType = ref<'Schema' | 'Json'>('Schema');
const jsonSchemaRef = ref<InstanceType<typeof MsJsonSchema>>(); const jsonSchemaRef = ref<InstanceType<typeof MsJsonSchema>>();
function previewJsonSchema() { function previewJsonSchema() {
if (jsonType.value === 'Schema') { if (innerParams.value.jsonBody.enableJsonSchema) {
jsonSchemaRef.value?.previewSchema(); jsonSchemaRef.value?.previewSchema();
} }
} }
/**
* 自动转换json schema为json
*/
async function autoMakeJson() {
if (!innerParams.value.jsonBody.enableJsonSchema) {
try {
bodyLoading.value = true;
let schema = innerParams.value.jsonBody.jsonSchema;
if (!schema && innerParams.value.jsonBody.jsonSchemaTableData) {
// jsonSchema json schema
schema = parseTableDataToJsonSchema(innerParams.value.jsonBody.jsonSchemaTableData[0]);
}
if (schema) {
// json schema json
const res = await convertJsonSchemaToJson(schema);
innerParams.value.jsonBody.jsonValue = JSON.stringify(res);
} else {
Message.warning(t('apiTestManagement.pleaseInputJsonSchema'));
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
bodyLoading.value = false;
}
}
}
// //
const currentBodyCode = computed({ const currentBodyCode = computed({
get() { get() {

View File

@ -432,6 +432,7 @@
import { TabItem } from '@/components/pure/ms-editable-tab/types'; import { TabItem } from '@/components/pure/ms-editable-tab/types';
import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue'; import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import { parseTableDataToJsonSchema } from '@/components/pure/ms-json-schema/utils';
import MsTab from '@/components/pure/ms-tab/index.vue'; import MsTab from '@/components/pure/ms-tab/index.vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue'; import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import assertion from '@/components/business/ms-assertion/index.vue'; import assertion from '@/components/business/ms-assertion/index.vue';
@ -990,7 +991,7 @@
*/ */
function makeRequestParams(executeType?: 'localExec' | 'serverExec') { function makeRequestParams(executeType?: 'localExec' | 'serverExec') {
const isExecute = executeType === 'localExec' || executeType === 'serverExec'; const isExecute = executeType === 'localExec' || executeType === 'serverExec';
const { formDataBody, wwwFormBody } = requestVModel.value.body; const { formDataBody, wwwFormBody, jsonBody } = requestVModel.value.body;
const polymorphicName = protocolOptions.value.find( const polymorphicName = protocolOptions.value.find(
(e) => e.value === requestVModel.value.protocol (e) => e.value === requestVModel.value.protocol
)?.polymorphicName; // )?.polymorphicName; //
@ -1023,6 +1024,13 @@
wwwFormBody: { wwwFormBody: {
formValues: realWwwFormBodyValues, formValues: realWwwFormBodyValues,
}, },
jsonBody: {
jsonValue: jsonBody.jsonValue,
enableJsonSchema: jsonBody.enableJsonSchema,
jsonSchema: jsonBody.jsonSchemaTableData
? parseTableDataToJsonSchema(jsonBody.jsonSchemaTableData[0])
: undefined,
},
}, },
headers: filterKeyValParams(requestVModel.value.headers, defaultHeaderParamsItem, isExecute).validParams, headers: filterKeyValParams(requestVModel.value.headers, defaultHeaderParamsItem, isExecute).validParams,
method: requestVModel.value.method, method: requestVModel.value.method,
@ -1058,6 +1066,16 @@
response: requestVModel.value.responseDefinition?.map((e) => ({ response: requestVModel.value.responseDefinition?.map((e) => ({
...e, ...e,
headers: filterKeyValParams(e.headers, defaultKeyValueParamItem, isExecute).validParams, headers: filterKeyValParams(e.headers, defaultKeyValueParamItem, isExecute).validParams,
body: {
...e.body,
jsonBody: {
jsonValue: e.body.jsonBody.jsonValue,
enableJsonSchema: jsonBody.enableJsonSchema,
jsonSchema: e.body.jsonBody.jsonSchemaTableData
? parseTableDataToJsonSchema(e.body.jsonBody.jsonSchemaTableData[0])
: undefined,
},
},
})), })),
}; };
} else { } else {
@ -1172,9 +1190,6 @@
() => requestVModel.value.id, () => requestVModel.value.id,
async () => { async () => {
isSwitchingContent.value = true; // isSwitchingContent.value = true; //
nextTick(() => {
isSwitchingContent.value = false; //
});
if (requestVModel.value.protocol !== 'HTTP') { if (requestVModel.value.protocol !== 'HTTP') {
requestVModel.value.activeTab = RequestComposition.PLUGIN; requestVModel.value.activeTab = RequestComposition.PLUGIN;
if (protocolOptions.value.length === 0) { if (protocolOptions.value.length === 0) {
@ -1206,6 +1221,9 @@
requestVModel.value.executeLoading = false; requestVModel.value.executeLoading = false;
delete temporaryResponseMap[props.request.reportId]; delete temporaryResponseMap[props.request.reportId];
} }
nextTick(() => {
isSwitchingContent.value = false; //
});
}, },
{ {
immediate: true, immediate: true,

View File

@ -96,20 +96,48 @@
{{ ResponseBodyFormat[item].toLowerCase() }} {{ ResponseBodyFormat[item].toLowerCase() }}
</a-radio> </a-radio>
</a-radio-group> </a-radio-group>
<!-- <div v-if="activeResponse.body.bodyType === ResponseBodyFormat.JSON" class="ml-auto flex items-center"> <div
<a-radio-group v-if="activeResponse.body.bodyType === ResponseBodyFormat.JSON"
v-model:model-value="activeResponse.body.jsonBody.enableJsonSchema" class="ml-auto flex items-center gap-[8px]"
size="mini" >
@change="emit('change')" <MsButton
type="text"
class="!mr-0"
:class="
activeResponse.body.jsonBody.enableJsonSchema
? 'font-medium !text-[rgb(var(--primary-5))]'
: '!text-[var(--color-text-4)]'
"
@click="activeResponse.body.jsonBody.enableJsonSchema = true"
> >
<a-radio :value="false">Json</a-radio> Schema
<a-radio class="mr-0" :value="true"> Json Schema </a-radio> </MsButton>
</a-radio-group> <a-divider :margin="0" direction="vertical"></a-divider>
<div class="flex items-center gap-[8px]"> <MsButton
<a-switch v-model:model-value="activeResponse.body.jsonBody.enableTransition" size="small" type="line" /> type="text"
{{ t('apiTestManagement.dynamicConversion') }} class="!mr-0"
</div> :class="
</div> --> !activeResponse.body.jsonBody.enableJsonSchema
? 'font-medium !text-[rgb(var(--primary-5))]'
: '!text-[var(--color-text-4)]'
"
@click="activeResponse.body.jsonBody.enableJsonSchema = false"
>
Json
</MsButton>
<a-button
v-show="activeResponse.body.jsonBody.enableJsonSchema"
type="outline"
class="arco-btn-outline--secondary ml-[16px] px-[8px]"
size="small"
@click="previewJsonSchema"
>
<div class="flex items-center gap-[8px]">
<icon-eye />
{{ t('common.preview') }}
</div>
</a-button>
</div>
</div> </div>
<div <div
v-if=" v-if="
@ -118,12 +146,14 @@
) )
" "
> >
<!-- <MsJsonSchema <MsJsonSchema
v-if="activeResponse.body.jsonBody.enableJsonSchema" v-if="activeResponse.body.jsonBody.enableJsonSchema"
:data="activeResponse.body.jsonBody.jsonSchema" ref="jsonSchemaRef"
:columns="jsonSchemaColumns" v-model:data="activeResponse.body.jsonBody.jsonSchemaTableData"
/> --> v-model:selectedKeys="selectedKeys"
/>
<MsCodeEditor <MsCodeEditor
v-else
ref="responseEditorRef" ref="responseEditorRef"
v-model:model-value="currentBodyCode" v-model:model-value="currentBodyCode"
:language="currentCodeLanguage" :language="currentCodeLanguage"
@ -134,6 +164,11 @@
:show-charset-change="false" :show-charset-change="false"
show-code-format show-code-format
> >
<template #rightTitle>
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="autoMakeJson">
<div class="text-[var(--color-text-1)]">{{ t('apiTestManagement.autoMake') }}</div>
</a-button>
</template>
</MsCodeEditor> </MsCodeEditor>
</div> </div>
<div v-else> <div v-else>
@ -194,14 +229,16 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue'; import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import { LanguageEnum } from '@/components/pure/ms-code-editor/types'; import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue'; import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
import { TabItem } from '@/components/pure/ms-editable-tab/types'; import { TabItem } from '@/components/pure/ms-editable-tab/types';
// import { FormTableColumn } from '@/components/pure/ms-form-table/index.vue'; import MsJsonSchema from '@/components/pure/ms-json-schema/index.vue';
// import MsJsonSchema from '@/components/pure/ms-json-schema/index.vue'; import { parseSchemaToJsonSchemaTableData, parseTableDataToJsonSchema } from '@/components/pure/ms-json-schema/utils';
import MsMoreAction from '@/components/pure/ms-table-more-action/index.vue'; import MsMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types'; import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { MsFileItem } from '@/components/pure/ms-upload/types'; import { MsFileItem } from '@/components/pure/ms-upload/types';
@ -209,6 +246,7 @@
import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue'; import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import popConfirm from '@/views/api-test/components/popConfirm.vue'; import popConfirm from '@/views/api-test/components/popConfirm.vue';
import { convertJsonSchemaToJson } from '@/api/modules/api-test/management';
import { responseHeaderOption } from '@/config/apiTest'; import { responseHeaderOption } from '@/config/apiTest';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
@ -350,20 +388,54 @@
emit('change'); emit('change');
} }
// const jsonSchemaColumns: FormTableColumn[] = [ const jsonSchemaRef = ref<InstanceType<typeof MsJsonSchema>>();
// { const bodyLoading = ref(false);
// title: 'apiTestManagement.paramName', const selectedKeys = ref<string[]>([]);
// dataIndex: 'key',
// slotName: 'key', watchEffect(() => {
// inputType: 'input', if (activeResponse.value.body.jsonBody.jsonSchema) {
// }, const { result, ids } = parseSchemaToJsonSchemaTableData(activeResponse.value.body.jsonBody.jsonSchema);
// { activeResponse.value.body.jsonBody.jsonSchemaTableData = result;
// title: 'apiTestManagement.paramVal', selectedKeys.value = ids;
// dataIndex: 'value', } else {
// slotName: 'value', activeResponse.value.body.jsonBody.jsonSchemaTableData = [];
// inputType: 'input', selectedKeys.value = [];
// }, }
// ]; });
function previewJsonSchema() {
if (activeResponse.value.body.jsonBody.enableJsonSchema) {
jsonSchemaRef.value?.previewSchema();
}
}
/**
* 自动转换json schema为json
*/
async function autoMakeJson() {
if (!activeResponse.value.body.jsonBody.enableJsonSchema) {
try {
bodyLoading.value = true;
let schema = activeResponse.value.body.jsonBody.jsonSchema;
if (!schema && activeResponse.value.body.jsonBody.jsonSchemaTableData) {
// jsonSchema json schema
schema = parseTableDataToJsonSchema(activeResponse.value.body.jsonBody.jsonSchemaTableData[0]);
}
if (schema) {
// json schema json
const res = await convertJsonSchemaToJson(schema);
activeResponse.value.body.jsonBody.jsonValue = JSON.stringify(res);
} else {
Message.warning(t('apiTestManagement.pleaseInputJsonSchema'));
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
bodyLoading.value = false;
}
}
}
// //
const currentBodyCode = computed({ const currentBodyCode = computed({

View File

@ -183,8 +183,9 @@
if (!item.body.jsonBody) { if (!item.body.jsonBody) {
item.body.jsonBody = { item.body.jsonBody = {
jsonValue: '', jsonValue: '',
enableJsonSchema: false, enableJsonSchema: true,
enableTransition: false, enableTransition: false,
jsonSchemaTableData: [],
}; };
if (!item.body.xmlBody) { if (!item.body.xmlBody) {
item.body.xmlBody = { item.body.xmlBody = {

View File

@ -166,6 +166,8 @@ export default {
'apiTestManagement.regex': 'Regular Expression', 'apiTestManagement.regex': 'Regular Expression',
'apiTestManagement.caseTotal': 'Case total', 'apiTestManagement.caseTotal': 'Case total',
'apiTestManagement.requestTypeTip': 'Note: Batch request type changes apply only to HTTP requests.', 'apiTestManagement.requestTypeTip': 'Note: Batch request type changes apply only to HTTP requests.',
'apiTestManagement.autoMake': 'Auto Generate',
'apiTestManagement.pleaseInputJsonSchema': 'Please enter Schema first before automatically generating it.',
'case.execute.selectEnv': 'Select Environment', 'case.execute.selectEnv': 'Select Environment',
'case.execute.defaultEnv': 'Default Environment', 'case.execute.defaultEnv': 'Default Environment',
'case.execute.newEnv': 'New Environment', 'case.execute.newEnv': 'New Environment',

View File

@ -159,6 +159,8 @@ export default {
'apiTestManagement.regex': '正则表达式', 'apiTestManagement.regex': '正则表达式',
'apiTestManagement.caseTotal': '用例数', 'apiTestManagement.caseTotal': '用例数',
'apiTestManagement.requestTypeTip': '注批量修改请求类型仅对HTTP协议的请求生效', 'apiTestManagement.requestTypeTip': '注批量修改请求类型仅对HTTP协议的请求生效',
'apiTestManagement.autoMake': '自动生成',
'apiTestManagement.pleaseInputJsonSchema': '请先输入 Schema 后再进行自动生成',
'case.execute.selectEnv': '环境选择', 'case.execute.selectEnv': '环境选择',
'case.execute.defaultEnv': '默认环境', 'case.execute.defaultEnv': '默认环境',
'case.execute.newEnv': '新环境', 'case.execute.newEnv': '新环境',