feat(json-schema): ms-json-schema组件-细节交互&预览

This commit is contained in:
baiqi 2024-07-02 17:56:34 +08:00 committed by 刘瑞斌
parent cc26849807
commit ce391aac3a
19 changed files with 484 additions and 157 deletions

View File

@ -603,7 +603,7 @@
showDrag: true,
columnSelectorDisabled: true,
addLineDisabled: true,
typeOptions: [
options: [
{ label: 'object', value: 'object' },
{ label: 'array', value: 'array' },
{ label: 'string', value: 'string' },

View File

@ -828,7 +828,7 @@
* @param fullJson 脑图导出的完整数据
* @param callback 保存成功回调
*/
async function handleMinderSave(fullJson: MinderJson, callback: () => void) {
async function handleMinderSave(fullJson: MinderJson, callback: (refersh: boolean) => void) {
try {
loading.value = true;
await saveCaseMinder(makeMinderParams(fullJson));
@ -836,7 +836,7 @@
Message.success(t('common.saveSuccess'));
resetMinderParams();
emit('save');
callback();
callback(true);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);

View File

@ -1,61 +1,63 @@
<template>
<a-popover position="tl" :disabled="!modelValue || modelValue.trim() === ''" class="ms-params-input-popover">
<template #content>
<div v-if="props.title" class="ms-params-popover-title">
{{ props.title }}
</div>
<div class="ms-params-popover-value">
{{ modelValue }}
</div>
</template>
<a-input
v-if="props.type === 'input'"
ref="inputRef"
v-model:model-value="modelValue"
:class="props.class"
:disabled="props.disabled"
:size="props.size"
:max-length="props.maxLength"
:placeholder="props.placeholder"
:trigger-props="{ contentClass: 'ms-form-table-input-trigger' }"
:allow-clear="props.allowClear"
@input="(val) => emit('input', val)"
@change="(val) => emit('change', val)"
/>
<a-textarea
v-else
ref="inputRef"
v-model:model-value="modelValue"
:class="props.class"
:disabled="props.disabled"
:size="props.size"
:placeholder="props.placeholder"
:max-length="props.maxLength"
:auto-size="{ minRows: 1, maxRows: 1 }"
:allow-clear="props.allowClear"
@input="(val) => emit('input', val)"
@change="(val) => emit('change', val)"
/>
</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-textarea>
</a-modal>
<div>
<a-popover position="tl" :disabled="!modelValue || modelValue.trim() === ''" class="ms-params-input-popover">
<template #content>
<div v-if="props.title" class="ms-params-popover-title">
{{ props.title }}
</div>
<div class="ms-params-popover-value">
{{ props.popoverTitle || modelValue }}
</div>
</template>
<a-input
v-if="props.type === 'input'"
ref="inputRef"
v-model:model-value="modelValue"
:class="props.class"
:disabled="props.disabled"
:size="props.size"
:max-length="props.maxLength"
:placeholder="props.placeholder"
:trigger-props="{ contentClass: 'ms-form-table-input-trigger' }"
:allow-clear="props.allowClear"
@input="(val) => emit('input', val)"
@change="(val) => emit('change', val)"
/>
<a-textarea
v-else
ref="inputRef"
v-model:model-value="modelValue"
:class="props.class"
:disabled="props.disabled"
:size="props.size"
:placeholder="props.placeholder"
:max-length="props.maxLength"
:auto-size="{ minRows: 1, maxRows: 1 }"
:allow-clear="props.allowClear"
@input="(val) => emit('input', val)"
@change="(val) => emit('change', val)"
/>
</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>
</template>
<script setup lang="ts">
@ -67,6 +69,7 @@
defineProps<{
type?: 'input' | 'textarea';
title?: string;
popoverTitle?: string;
placeholder?: string;
disabled?: boolean;
size?: 'small' | 'large' | 'medium' | 'mini';
@ -85,7 +88,6 @@
const emit = defineEmits<{
(e: 'input', val: string): void;
(e: 'change', val: string): void;
(e: 'dblclick'): void;
}>();
const { t } = useI18n();

View File

@ -305,6 +305,7 @@
theme: currentTheme.value,
lineNumbersMinChars: 3,
lineDecorationsWidth: 0,
tabSize: 2,
});
editor.getModel()?.setEOL(monaco.editor.EndOfLineSequence.LF); //

View File

@ -59,8 +59,9 @@
<div>*</div>
</MsButton>
</a-tooltip>
<div v-if="item.isNull && item.isNull(record)" class="ms-form-table-td-text">-</div>
<a-input
v-if="item.inputType === 'input'"
v-else-if="item.inputType === 'input'"
v-model:model-value="record[item.dataIndex as string]"
:placeholder="t(item.locale)"
class="ms-form-table-input"
@ -71,7 +72,7 @@
<a-select
v-else-if="item.inputType === 'select'"
v-model:model-value="record[item.dataIndex as string]"
:options="item.typeOptions || []"
:options="item.options || []"
class="ms-form-table-input w-full"
:size="item.size || 'medium'"
@change="() => handleFormChange(record, rowIndex, item)"
@ -249,6 +250,7 @@
step?: number;
precision?: number;
valueFormat?: (record: Record<string, any>) => string; // inputTypetext
isNull?: (record: Record<string, any>) => boolean; // -
[key: string]: any; //
}
@ -512,6 +514,9 @@
}
}
}
.ms-form-table-td-text {
padding: 0 8px;
}
.arco-table-col-fixed-right {
.arco-table-cell {
padding: 0 8px !important;

View File

@ -16,15 +16,19 @@
:scroll="{ x: 'max-content' }"
show-setting
class="ms-json-schema"
@select="handleSelect"
@select="
(rowKeys: (string | number)[], rowKey: string | number, record: Record<string, any>) =>
handleSelect(rowKeys, rowKey, record as JsonSchemaTableItem)
"
>
<template #batchAddTitle>
<MsButton type="text" size="mini" class="!mr-0" @click="batchAdd">
{{ t('apiTestDebug.batchAdd') }}
</MsButton>
</template>
<template #title="{ record, columnConfig }">
<span v-if="record.title === 'root'" class="px-[8px]">root</span>
<template #title="{ record, columnConfig, rowIndex }">
<span v-if="record.id === 'root'" class="px-[8px]">root</span>
<span v-else-if="record.parent?.type === 'array'" class="px-[8px]">{{ rowIndex }}</span>
<a-popover
v-else
position="tl"
@ -73,8 +77,9 @@
@change="handleTypeChange(record)"
/>
</template>
<template #value="{ record }">
<MsParamsInput v-model:value="record.value" size="medium" @dblclick="() => quickInputParams(record)" />
<template #example="{ record }">
<div v-if="['object', 'array', 'null'].includes(record.type)" class="ms-form-table-td-text">-</div>
<MsParamsInput v-else v-model:value="record.example" size="medium" @dblclick="() => quickInputParams(record)" />
</template>
<template #minLength="{ record }">
<a-input-number
@ -85,7 +90,7 @@
:precision="0"
size="medium"
/>
<div v-else class="ms-json-schema-td-text">-</div>
<div v-else class="ms-form-table-td-text">-</div>
</template>
<template #maxLength="{ record }">
<a-input-number
@ -96,7 +101,7 @@
:precision="0"
size="medium"
/>
<div v-else class="ms-json-schema-td-text">-</div>
<div v-else class="ms-form-table-td-text">-</div>
</template>
<template #minimum="{ record }">
<a-input-number
@ -113,7 +118,7 @@
:step="1"
:precision="0"
/>
<div v-else class="ms-json-schema-td-text">-</div>
<div v-else class="ms-form-table-td-text">-</div>
</template>
<template #maximum="{ record }">
<a-input-number
@ -130,7 +135,31 @@
:step="1"
:precision="0"
/>
<div v-else class="ms-json-schema-td-text">-</div>
<div v-else class="ms-form-table-td-text">-</div>
</template>
<template #minItems="{ record }">
<a-input-number
v-if="record.type === 'array'"
v-model:model-value="record.minItems"
class="ms-form-table-input-number"
size="medium"
:min="0"
:step="1"
:precision="0"
/>
<div v-else class="ms-form-table-td-text">-</div>
</template>
<template #maxItems="{ record }">
<a-input-number
v-if="record.type === 'array'"
v-model:model-value="record.maxItems"
class="ms-form-table-input-number"
size="medium"
:min="0"
:step="1"
:precision="0"
/>
<div v-else class="ms-form-table-td-text">-</div>
</template>
<template #defaultValue="{ record }">
<a-input-number
@ -163,7 +192,7 @@
},
]"
/>
<div v-else-if="['object', 'array', 'null'].includes(record.type)" class="ms-json-schema-td-text"> - </div>
<div v-else-if="['object', 'array', 'null'].includes(record.type)" class="ms-form-table-td-text"> - </div>
<a-input
v-else
v-model:model-value="record.defaultValue"
@ -171,6 +200,18 @@
class="ms-form-table-input"
/>
</template>
<template #enumValues="{ record }">
<div v-if="['object', 'array', 'null', 'boolean'].includes(record.type)" class="ms-form-table-td-text">-</div>
<MsQuickInput
v-else
v-model:model-value="record.enumValues"
:title="t('ms.json.schema.enum')"
:popover-title="JSON.stringify(record.enumValues.split('\n'))"
class="ms-form-table-input"
type="textarea"
>
</MsQuickInput>
</template>
<template #action="{ record, rowIndex }">
<div class="flex w-full items-center gap-[8px]">
<a-tooltip :content="t('common.advancedSettings')">
@ -233,14 +274,19 @@
asterisk-position="end"
>
<a-input
v-model:model-value="activeRecord.name"
v-model:model-value="activeRecord.title"
:max-length="255"
:placeholder="t('common.pleaseInput')"
:disabled="activeRecord.id === 'root'"
@change="handleSettingFormChange"
/>
</a-form-item>
<a-form-item :label="t('common.desc')">
<a-textarea v-model:model-value="activeRecord.description" :placeholder="t('common.pleaseInput')" />
<a-textarea
v-model:model-value="activeRecord.description"
:placeholder="t('common.pleaseInput')"
@change="handleSettingFormChange"
/>
</a-form-item>
<template v-if="!['object', 'array', 'null'].includes(activeRecord.type)">
<div class="flex items-center justify-between gap-[24px]">
@ -252,6 +298,7 @@
:min="0"
:step="1"
:precision="0"
@change="handleSettingFormChange"
/>
</a-form-item>
<a-form-item :label="t('ms.json.schema.maxLength')" class="w-[144px]">
@ -261,6 +308,7 @@
:min="0"
:step="1"
:precision="0"
@change="handleSettingFormChange"
/>
</a-form-item>
</template>
@ -272,8 +320,14 @@
mode="button"
:step="1"
:precision="0"
@change="handleSettingFormChange"
/>
<a-input-number
v-else
v-model:model-value="activeRecord.minimum"
mode="button"
@change="handleSettingFormChange"
/>
<a-input-number v-else v-model:model-value="activeRecord.minimum" mode="button" />
</a-form-item>
<a-form-item :label="t('ms.json.schema.maximum')" class="w-[144px]">
<a-input-number
@ -282,8 +336,14 @@
mode="button"
:step="1"
:precision="0"
@change="handleSettingFormChange"
/>
<a-input-number
v-else
v-model:model-value="activeRecord.maximum"
mode="button"
@change="handleSettingFormChange"
/>
<a-input-number v-else v-model:model-value="activeRecord.maximum" mode="button" />
</a-form-item>
</template>
<a-form-item :label="t('ms.json.schema.default')" class="flex-1">
@ -292,6 +352,7 @@
v-model:model-value="activeRecord.defaultValue"
mode="button"
:placeholder="t('common.pleaseInput')"
@change="handleSettingFormChange"
/>
<a-input-number
v-else-if="activeRecord.type === 'integer'"
@ -300,33 +361,71 @@
:placeholder="t('common.pleaseInput')"
:step="1"
:precision="0"
@change="handleSettingFormChange"
/>
<a-input
v-else
v-model:model-value="activeRecord.defaultValue"
:placeholder="t('common.pleaseInput')"
@change="handleSettingFormChange"
/>
<a-input v-else v-model:model-value="activeRecord.defaultValue" :placeholder="t('common.pleaseInput')" />
</a-form-item>
</div>
<template v-if="activeRecord.type !== 'boolean'">
<a-form-item :label="t('ms.json.schema.enum')">
<a-textarea v-model:model-value="activeRecord.enum" :placeholder="t('ms.json.schema.enumPlaceholder')" />
<a-textarea
v-model:model-value="activeRecord.enumValues"
:placeholder="t('ms.json.schema.enumPlaceholder')"
@change="handleSettingFormChange"
/>
</a-form-item>
<a-form-item :label="t('ms.json.schema.regex')">
<a-input v-model:model-value="activeRecord.regex" :placeholder="t('ms.json.schema.regexPlaceholder')" />
<a-input
v-model:model-value="activeRecord.regex"
:placeholder="t('ms.json.schema.regexPlaceholder', { reg: '/<title(.*?)</title>' })"
@change="handleSettingFormChange"
/>
</a-form-item>
<a-form-item :label="t('ms.json.schema.format')">
<a-select
v-model:model-value="activeRecord.format"
:placeholder="t('common.pleaseSelect')"
:options="formatOptions"
@change="handleSettingFormChange"
/>
</a-form-item>
</template>
</template>
<div v-if="activeRecord.type === 'array'" class="flex items-center gap-[24px]">
<a-form-item :label="t('ms.json.schema.minItems')" class="w-[144px]">
<a-input-number
v-model:model-value="activeRecord.minItems"
mode="button"
:min="0"
:step="1"
:precision="0"
@change="handleSettingFormChange"
/>
</a-form-item>
<a-form-item :label="t('ms.json.schema.maxItems')" class="w-[144px]">
<a-input-number
v-model:model-value="activeRecord.maxItems"
mode="button"
:min="0"
:step="1"
:precision="0"
@change="handleSettingFormChange"
/>
</a-form-item>
</div>
<div>
<div class="mb-[8px]">{{ t('ms.json.schema.preview') }}</div>
<MsCodeEditor
v-model:model-value="activePreviewValue"
theme="vs"
height="300px"
height="500px"
:show-full-screen="false"
:language="LanguageEnum.JSON"
read-only
>
</MsCodeEditor>
@ -362,6 +461,16 @@
</template>
</MsCodeEditor>
</MsDrawer>
<MsDrawer v-model:visible="previewDrawerVisible" :width="600" :title="t('common.preview')" :footer="false">
<MsCodeEditor
v-model:model-value="activePreviewValue"
theme="vs"
height="100%"
:language="LanguageEnum.JSON"
:show-full-screen="false"
read-only
/>
</MsDrawer>
</template>
<script setup lang="ts">
@ -374,21 +483,23 @@
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsFormTable, { FormTableColumn } from '@/components/pure/ms-form-table/index.vue';
import MsParamsInput from '@/components/business/ms-params-input/index.vue';
import MsQuickInput from '@/components/business/ms-quick-input/index.vue';
import { useI18n } from '@/hooks/useI18n';
import { getGenerateId, traverseTree } from '@/utils';
import { TableKeyEnum } from '@/enums/tableEnum';
import { JsonSchemaItem } from './types';
import { JsonSchemaTableItem } from './types';
import { convertToJsonSchema } from './utils';
const { t } = useI18n();
const defaultItem: JsonSchemaItem = {
const defaultItem: JsonSchemaTableItem = {
id: '',
type: 'string',
title: '',
value: '',
example: '',
description: '',
enable: true,
defaultValue: '',
@ -401,13 +512,13 @@
format: undefined,
children: undefined,
};
const data = defineModel<JsonSchemaItem[]>('data', {
const data = defineModel<JsonSchemaTableItem[]>('data', {
default: () => [
{
id: 'root',
title: 'root',
type: 'object',
value: '',
example: '',
description: '',
enable: true,
defaultValue: '',
@ -508,8 +619,8 @@
},
{
title: t('ms.json.schema.value'),
dataIndex: 'value',
slotName: 'value',
dataIndex: 'example',
slotName: 'example',
addLineDisabled: true,
columnSelectorDisabled: true,
},
@ -565,6 +676,30 @@
showInTable: false,
width: 120,
},
{
title: t('ms.json.schema.minItems'),
dataIndex: 'minItems',
slotName: 'minItems',
inputType: 'inputNumber',
size: 'medium',
min: 0,
precision: 0,
addLineDisabled: true,
showInTable: false,
width: 120,
},
{
title: t('ms.json.schema.maxItems'),
dataIndex: 'maxItems',
slotName: 'maxItems',
inputType: 'inputNumber',
size: 'medium',
min: 0,
precision: 0,
addLineDisabled: true,
showInTable: false,
width: 120,
},
{
title: t('ms.json.schema.default'),
dataIndex: 'defaultValue',
@ -576,8 +711,8 @@
},
{
title: t('ms.json.schema.enum'),
dataIndex: 'enum',
slotName: 'enum',
dataIndex: 'enumValues',
slotName: 'enumValues',
inputType: 'textarea',
size: 'medium',
addLineDisabled: true,
@ -591,6 +726,7 @@
size: 'medium',
addLineDisabled: true,
showInTable: false,
isNull: (record) => ['object', 'array', 'null', 'boolean'].includes(record.type),
},
{
title: t('ms.json.schema.format'),
@ -601,6 +737,7 @@
options: formatOptions,
addLineDisabled: true,
showInTable: false,
isNull: (record) => ['object', 'array', 'null', 'boolean'].includes(record.type),
},
{
title: '',
@ -617,8 +754,8 @@
/**
* 获取类型选项根节点只能是 object array
*/
function getTypeOptions(record: Record<string, any>) {
if (record.name === 'root') {
function getTypeOptions(record: JsonSchemaTableItem) {
if (record.id === 'root') {
return typeOptions.filter((item) => ['object', 'array'].includes(item.value as string));
}
return typeOptions;
@ -627,7 +764,7 @@
/**
* 处理类型变化
*/
function handleTypeChange(record: Record<string, any>) {
function handleTypeChange(record: JsonSchemaTableItem) {
if (record.type === 'object' || record.type === 'array') {
if (!record.children) {
//
@ -641,7 +778,7 @@
/**
* 添加子节点
*/
function addChild(record: Record<string, any>) {
function addChild(record: JsonSchemaTableItem) {
if (!record.children) {
record.children = [];
}
@ -664,8 +801,8 @@
/**
* 删除行
*/
function deleteLine(record: Record<string, any>, rowIndex: number) {
if (record.parent) {
function deleteLine(record: JsonSchemaTableItem, rowIndex: number) {
if (record.parent?.children) {
record.parent.children.splice(rowIndex, 1);
} else {
data.value.splice(rowIndex, 1);
@ -675,11 +812,11 @@
/**
* 行选择处理
*/
function handleSelect(rowKeys: (string | number)[], rowKey: string | number, record: Record<string, any>) {
function handleSelect(rowKeys: (string | number)[], rowKey: string | number, record: JsonSchemaTableItem) {
nextTick(() => {
if (record.enable && record.children && record.children.length > 0) {
//
traverseTree(record.children, (item: Record<string, any>) => {
traverseTree<JsonSchemaTableItem>(record.children, (item) => {
item.enable = true;
if (!selectedKeys.value.includes(item.id)) {
selectedKeys.value.push(item.id);
@ -704,12 +841,26 @@
const activeRecord = ref<any>({});
const activePreviewValue = ref('');
function openSetting(record: Record<string, any>) {
function openSetting(record: JsonSchemaTableItem) {
// parent children
activeRecord.value = {
...record,
};
settingDrawerVisible.value = true;
try {
activePreviewValue.value = JSON.stringify(convertToJsonSchema(record, record.id === 'root'));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
activePreviewValue.value = t('ms.json.schema.convertFailed');
} finally {
settingDrawerVisible.value = true;
}
}
function handleSettingFormChange() {
activePreviewValue.value = JSON.stringify(
convertToJsonSchema(activeRecord.value, activeRecord.value.id === 'root')
);
}
/**
@ -717,12 +868,12 @@
*/
function applySetting() {
if (activeRecord.value.id === 'root') {
data.value[0] = activeRecord.value;
data.value = [{ ...activeRecord.value }];
} else {
const brothers = activeRecord.value.parent?.children || [];
const index = brothers.findIndex((item: any) => item.id === activeRecord.value.id);
if (index > -1) {
brothers.splice(index, 1, activeRecord.value);
brothers.splice(index, 1, { ...activeRecord.value });
}
}
settingDrawerVisible.value = false;
@ -748,13 +899,20 @@
showQuickInputParam.value = false;
clearQuickInputParam();
}
const previewDrawerVisible = ref(false);
function previewSchema() {
previewDrawerVisible.value = true;
activePreviewValue.value = JSON.stringify(convertToJsonSchema(data.value[0]));
}
defineExpose({
previewSchema,
});
</script>
<style lang="less">
.ms-json-schema {
.ms-json-schema-td-text {
padding: 0 8px;
}
.ms-json-schema-icon-button {
@apply !mr-0;
&:hover {

View File

@ -13,9 +13,12 @@ export default {
'ms.json.schema.enum': '枚举值',
'ms.json.schema.enumPlaceholder': '1 行 1 个枚举值',
'ms.json.schema.regex': '正则表达式',
'ms.json.schema.regexPlaceholder': '如 /<title(.*?)</title>',
'ms.json.schema.regexPlaceholder': '如 {reg}',
'ms.json.schema.format': '格式化',
'ms.json.schema.preview': '预览',
'ms.json.schema.batchAdd': '批量添加',
'ms.json.schema.batchAddTip': '书写格式:"键":"值",如"nama":"natural"',
'ms.json.schema.convertFailed': '数据转换失败,请重试',
'ms.json.schema.minItems': '最小元素数量',
'ms.json.schema.maxItems': '最大元素数量',
};

View File

@ -1,22 +1,38 @@
export interface JsonSchemaItem {
export interface JsonSchemaCommon {
id: string;
title: string; // 参数名称
type: string; // 参数类型
description: string; // 参数描述
enable: boolean; // 是否启用
value: string; // 参数值
example: string; // 参数值
defaultValue: string | number | boolean; // 默认值
example?: Record<string, any>;
items?: string; // 子级,当 type 为array 时,使用该值
properties?: Record<string, any>; // 子级,当 type 为object 时,使用该值
required?: string[]; // 必填参数 这里的值是参数的title
pattern?: string; // 正则表达式
maxLength?: number;
minLength?: number;
minimum?: number;
maximum?: number;
minItems?: number;
maxItems?: number;
format?: string; // 格式化
enumValues?: string; // 参数值的枚举
// 前端渲染字段
children?: JsonSchemaItem[];
}
// json-schema 表格组件的表格项
export interface JsonSchemaTableItem extends JsonSchemaCommon {
required?: string[]; // 是否必填
children?: JsonSchemaTableItem[];
parent?: JsonSchemaTableItem; // 父级
enumValues?: string; // 参数值的枚举
}
// json-schema 规范的结构子项(表格组件转换后的结构)
export interface JsonSchemaItem extends JsonSchemaCommon {
items?: JsonSchemaItem[]; // 子级,当 type 为array 时,使用该值
properties?: Record<string, JsonSchemaItem>; // 子级,当 type 为object 时,使用该值
required?: string[]; // 必填的字段名
enumValues?: string[]; // 参数值的枚举
}
// json-schema 规范的结构(表格组件转换后的结构)
export interface JsonSchema {
type: string;
properties?: Record<string, JsonSchemaItem>;
items?: JsonSchemaItem[];
required?: string[];
}

View File

@ -0,0 +1,64 @@
import type { JsonSchema, JsonSchemaItem, JsonSchemaTableItem } from './types';
/**
* json-schema json-schema
* @param schemaItem
* @param isRoot
*/
export function convertToJsonSchema(
schemaItem: JsonSchemaTableItem,
isRoot: boolean = true
): JsonSchema | JsonSchemaItem {
let schema: JsonSchema | JsonSchemaItem = { type: schemaItem.type };
// 对于 null 类型,只设置 type 和 enable 属性
if (schemaItem.type === 'null') {
return {
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,
properties: {},
required: [],
};
schemaItem.children.forEach((child) => {
const childSchema = convertToJsonSchema(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) => convertToJsonSchema(child, false) as JsonSchemaItem),
};
}
}
return schema;
}
export default {};

View File

@ -203,7 +203,7 @@
data = cloneDeep(fullJson);
importJson.value = fullJson;
}
emit('save', data, () => {
emit('save', data, (refresh = false) => {
importJson.value.root.children = mapTree<MinderJsonNode>(
importJson.value.root.children || [],
(node, path, level) => ({
@ -216,12 +216,14 @@
},
})
);
// if (innerImportJson.value.treePath?.length > 1) {
// switchNode(innerImportJson.value.root.data);
// } else {
// innerImportJson.value = importJson.value;
// window.minder.importJson(importJson.value);
// }
if (refresh) {
if (innerImportJson.value.treePath?.length > 1) {
switchNode(innerImportJson.value.root.data);
} else {
innerImportJson.value = importJson.value;
window.minder.importJson(importJson.value);
}
}
minderStore.setMinderUnsaved(false);
floatMenuVisible.value = false;
});

View File

@ -7,6 +7,60 @@ import { Recordable } from '#/global';
export default function useLocalForage() {
const appStore = useAppStore();
/**
*
* @param val
*/
const serializeValue = (val: any): any => {
if (typeof val === 'function') {
return `function:${val.toString()}`;
}
if (val && typeof val === 'object' && !Array.isArray(val)) {
const newVal = { ...val };
Object.keys(newVal).forEach((key) => {
newVal[key] = serializeValue(newVal[key]);
});
return newVal;
}
if (Array.isArray(val)) {
return val.map((item) => serializeValue(item));
}
return val;
};
const deserializeFunction = (funcStr: string) => {
try {
// eslint-disable-next-line no-eval
const func = eval(`${funcStr}`);
return func;
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
return null;
}
};
/**
*
* @param val
*/
const deserializeValue = <T>(val: any): T | null => {
if (typeof val === 'string' && val.startsWith('function:')) {
return deserializeFunction(val.slice(9)) as T;
}
if (val && typeof val === 'object' && !Array.isArray(val)) {
const newVal = { ...val };
Object.keys(newVal).forEach((key) => {
newVal[key] = deserializeValue(newVal[key]);
});
return newVal as T;
}
if (Array.isArray(val)) {
return val.map((item) => deserializeValue(item) as T) as T;
}
return val;
};
/**
*
* @param key key
@ -19,7 +73,7 @@ export default function useLocalForage() {
if (!res) {
return null;
}
return res;
return deserializeValue<T>(res);
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
@ -36,7 +90,7 @@ export default function useLocalForage() {
const setItem = async (key: string, val: string | number | boolean | Recordable, notIsolatedByProject = false) => {
try {
const itemKey = notIsolatedByProject ? key : `${appStore.currentProjectId}-${key}`;
await localforage.setItem(itemKey, val);
await localforage.setItem(itemKey, serializeValue(val));
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);

View File

@ -835,7 +835,7 @@ if (!result){
title: 'apiTestDebug.paramType',
dataIndex: 'variableType',
slotName: 'variableType',
typeOptions: extractTypeOptions.map((item) => {
options: extractTypeOptions.map((item) => {
return {
label: t(item.label),
value: item.value,
@ -847,7 +847,7 @@ if (!result){
title: 'apiTestDebug.mode',
dataIndex: 'extractType',
slotName: 'extractType',
typeOptions: [
options: [
{
label: 'JSONPath',
value: RequestExtractExpressionEnum.JSON_PATH,
@ -867,7 +867,7 @@ if (!result){
title: 'apiTestDebug.range',
dataIndex: 'extractScope',
slotName: 'extractScope',
typeOptions: [
options: [
{
label: 'Body',
value: RequestExtractScope.BODY,

View File

@ -167,7 +167,7 @@
<a-select
v-model:model-value="record.paramType"
:disabled="props.disabledExceptParam"
:options="columnConfig.typeOptions || []"
:options="columnConfig.options || []"
class="ms-form-table-input w-full"
@change="(val) => handleTypeChange(val, record, rowIndex, columnConfig.addLineDisabled)"
/>
@ -177,7 +177,7 @@
<a-select
v-model:model-value="record.extractType"
:disabled="props.disabledExceptParam"
:options="columnConfig.typeOptions || []"
:options="columnConfig.options || []"
class="ms-form-table-input w-[110px]"
@change="() => addTableLine(rowIndex)"
/>
@ -187,7 +187,7 @@
<a-select
v-model:model-value="record.variableType"
:disabled="props.disabledExceptParam"
:options="columnConfig.typeOptions || []"
:options="columnConfig.options || []"
class="ms-form-table-input w-[110px]"
@change="() => addTableLine(rowIndex)"
/>
@ -197,7 +197,7 @@
<a-select
v-model:model-value="record.extractScope"
:disabled="props.disabledExceptParam || record.extractType !== RequestExtractExpressionEnum.REGEX"
:options="columnConfig.typeOptions || []"
:options="columnConfig.options || []"
class="ms-form-table-input w-[180px]"
@change="() => addTableLine(rowIndex)"
/>
@ -211,7 +211,7 @@
<a-select
v-model:model-value="record.scope"
:disabled="props.disabledExceptParam"
:options="columnConfig.typeOptions || []"
:options="columnConfig.options || []"
class="ms-form-table-input w-[180px]"
@change="(val) => handleScopeChange(val, record, rowIndex, columnConfig.addLineDisabled)"
/>
@ -625,7 +625,7 @@
isAutoComplete?: boolean; // key /
isNormal?: boolean; // value MsParamsInput
hasRequired?: boolean; // type required
typeOptions?: { label: string; value: string }[]; // type
options?: { label: string; value: string }[]; // type
typeTitleTooltip?: string | string[]; // type tooltip
hasDisable?: boolean; // operation enable
moreAction?: ActionsItem[]; // operation
@ -847,7 +847,7 @@
//
const id = getGenerateId();
const lastLineData = paramsData.value[rowIndex]; //
const selectColumnKeys = props.columns.filter((e) => e.typeOptions).map((e) => e.dataIndex); //
const selectColumnKeys = props.columns.filter((e) => e.options).map((e) => e.dataIndex); //
const nextLine = {
id,
enable: true, //

View File

@ -86,22 +86,36 @@
</div> -->
</div>
<div v-else class="h-[calc(100%-34px)]">
<div class="mb-[8px] flex items-center gap-[8px]">
<MsButton
type="text"
class="!mr-0"
:class="jsonType === 'Schema' ? 'font-medium !text-[rgb(var(--primary-5))]' : '!text-[var(--color-text-4)]'"
@click="jsonType = 'Schema'"
>Schema</MsButton
>
<a-divider :margin="0" direction="vertical"></a-divider>
<MsButton
type="text"
class="!mr-0"
:class="jsonType === 'Json' ? 'font-medium !text-[rgb(var(--primary-5))]' : '!text-[var(--color-text-4)]'"
@click="jsonType = 'Json'"
>Json</MsButton
<div class="mb-[8px] flex items-center justify-between">
<div class="flex items-center gap-[8px]">
<MsButton
type="text"
class="!mr-0"
:class="jsonType === 'Schema' ? 'font-medium !text-[rgb(var(--primary-5))]' : '!text-[var(--color-text-4)]'"
@click="jsonType = 'Schema'"
>Schema</MsButton
>
<a-divider :margin="0" direction="vertical"></a-divider>
<MsButton
type="text"
class="!mr-0"
:class="jsonType === 'Json' ? 'font-medium !text-[rgb(var(--primary-5))]' : '!text-[var(--color-text-4)]'"
@click="jsonType = 'Json'"
>Json</MsButton
>
</div>
<a-button
v-show="jsonType === 'Schema'"
type="outline"
class="arco-btn-outline--secondary px-[8px]"
size="small"
@click="previewJsonSchema"
>
<div class="flex items-center gap-[8px]">
<icon-eye />
{{ t('common.preview') }}
</div>
</a-button>
</div>
<MsCodeEditor
v-if="jsonType === 'Json'"
@ -116,7 +130,7 @@
is-adaptive
>
</MsCodeEditor>
<MsJsonSchema v-else />
<MsJsonSchema v-else ref="jsonSchemaRef" />
</div>
<batchAddKeyVal
v-if="showParamTable"
@ -222,7 +236,7 @@
}
}
const typeOptions = computed(() => {
const options = computed(() => {
const fullOptions = Object.values(RequestParamsType).map((val) => ({
label: val,
value: val,
@ -246,7 +260,7 @@
dataIndex: 'paramType',
slotName: 'paramType',
hasRequired: true,
typeOptions: typeOptions.value,
options: options.value,
width: 130,
},
{
@ -307,6 +321,14 @@
});
const jsonType = ref<'Schema' | 'Json'>('Schema');
const jsonSchemaRef = ref<InstanceType<typeof MsJsonSchema>>();
function previewJsonSchema() {
if (jsonType.value === 'Schema') {
jsonSchemaRef.value?.previewSchema();
}
}
//
const currentBodyCode = computed({
get() {

View File

@ -73,7 +73,7 @@
dataIndex: 'paramType',
slotName: 'paramType',
hasRequired: true,
typeOptions: Object.values(RequestParamsType)
options: Object.values(RequestParamsType)
.filter((val) => ![RequestParamsType.JSON, RequestParamsType.FILE].includes(val))
.map((val) => ({
label: val,

View File

@ -74,7 +74,7 @@
dataIndex: 'paramType',
slotName: 'paramType',
hasRequired: true,
typeOptions: Object.values(RequestParamsType)
options: Object.values(RequestParamsType)
.filter((val) => ![RequestParamsType.JSON, RequestParamsType.FILE].includes(val as RequestParamsType))
.map((val) => ({
label: val,

View File

@ -146,7 +146,7 @@
title: 'apiScenario.params.csvScoped',
dataIndex: 'scope',
slotName: 'scope',
typeOptions: [
options: [
{
label: t('apiScenario.scenario'),
value: 'SCENARIO',

View File

@ -90,7 +90,7 @@
title: 'apiScenario.params.type',
dataIndex: 'paramType',
slotName: 'paramType',
typeOptions: [
options: [
{
label: t('common.constant'),
value: 'CONSTANT',

View File

@ -90,7 +90,7 @@
showDrag: true,
hasRequired: false,
columnSelectorDisabled: true,
typeOptions: [
options: [
{
label: t('common.constant'),
value: 'CONSTANT',