From e548f9eb1f80658893384285cbcd7d041c42baaa Mon Sep 17 00:00:00 2001 From: lin-mt Date: Wed, 8 Jan 2025 23:05:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E7=82=B9=E5=87=BB=E9=AB=98=E7=BA=A7=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E5=90=8E=E7=9A=84=E8=A1=8C=E4=B8=BA=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BF=AE=E6=94=B9=E6=8C=87=E5=AE=9A=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E4=B8=8B=E7=9A=84JsonSchema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JsonSchemaEditor/SchemaItem/index.tsx | 28 +- src/JsonSchemaEditor/index.md | 49 ++- src/JsonSchemaEditor/index.tsx | 405 ++++++++++++---------- src/JsonSchemaEditor/types.ts | 5 + 4 files changed, 288 insertions(+), 199 deletions(-) diff --git a/src/JsonSchemaEditor/SchemaItem/index.tsx b/src/JsonSchemaEditor/SchemaItem/index.tsx index f2db85b..eda67ee 100644 --- a/src/JsonSchemaEditor/SchemaItem/index.tsx +++ b/src/JsonSchemaEditor/SchemaItem/index.tsx @@ -46,7 +46,11 @@ type SchemaItemProps = { isArrayItems?: boolean; isRequire?: boolean; schema: JSONSchema7; - changeSchema?: (namePath: number[], value: any, propertyName: string) => void; + changeSchema?: ( + namePath: number[], + value: any, + propertyName?: string, + ) => void; renameProperty?: (namePath: number[], name: string) => void; removeProperty?: (namePath: number[]) => void; addProperty?: (path: number[], isChild: boolean) => void; @@ -55,6 +59,11 @@ type SchemaItemProps = { requiredProperty: string, removed: boolean, ) => void; + handleAdvancedSettingClick?: ( + namePath: number[], + schema: JSONSchema7, + propertyName?: string, + ) => boolean; }; function SchemaItem(props: SchemaItemProps) { @@ -70,6 +79,7 @@ function SchemaItem(props: SchemaItemProps) { removeProperty, addProperty, isRequire, + handleAdvancedSettingClick, } = props; const [schema, setSchema] = useState(props.schema); @@ -273,6 +283,18 @@ function SchemaItem(props: SchemaItemProps) { icon={} style={{ color: 'green' }} onClick={() => { + if ( + handleAdvancedSettingClick && + !handleAdvancedSettingClick( + namePath, + schema, + isRoot || schema.type === 'object' + ? undefined + : propertyName, + ) + ) { + return; + } setFormSchema(schema); setAdvancedModal(!advancedModal); }} @@ -410,7 +432,7 @@ function SchemaItem(props: SchemaItemProps) { return; } if (isRoot || schema.type === 'object') { - changeSchema(namePath, { ...schema, ...formSchema }, 'root'); + changeSchema(namePath, { ...schema, ...formSchema }); setAdvancedModal(!advancedModal); return; } @@ -821,7 +843,7 @@ function SchemaItem(props: SchemaItemProps) { break; } if (changeSchema) { - changeSchema([], schema, 'root'); + changeSchema([], schema); setImportModal(!importModal); setImportValue(undefined); } diff --git a/src/JsonSchemaEditor/index.md b/src/JsonSchemaEditor/index.md index fec5bac..76021a9 100644 --- a/src/JsonSchemaEditor/index.md +++ b/src/JsonSchemaEditor/index.md @@ -33,7 +33,48 @@ loader.config({ monaco }); ## API -| 参数名称 | 描述 | 类型 | 默认值 | -| -------- | --------------------- | ---------------------------------- | ----------------------------------------------------------------- | -| onChange | JsonSchema 变更的回调 | (schema: JSONSchema7) => void | - | -| data | 初始化组件数据 | JSONSchema7 \| string \| undefined | `{"type": "object", "properties": {"field": {"type": "string"}}}` | +### onChange + +JsonSchema 变更的回调。 + +类型:`(schema: JSONSchema7) => void` + +默认值: `-` + +### data + +初始化组件数据。 + +类型:`JSONSchema7 | string | undefined` + +默认值: + +```json +{ + "type": "object", + "properties": { + "field": { + "type": "string" + } + } +} +``` + +### handleAdvancedSettingClick + +点击`高级设置`按钮的回调,返回`false`:不使用默认表单,返回`true`:使用默认表单。 + +类型:`(namePath: number[], schema: JSONSchema7, propertyName?: string) => boolean` + +默认值:`-` + +说明:一般结合`组件引用`实现点击高级设置按钮后的自定义需求。 + +## 组件引用(ref) + +```ts +export interface JsonSchemaEditorHandle { + /* 更新指定路径下的 JsonSchema */ + changeSchema: (namePath: number[], value: any, propertyName?: string) => void; +} +``` diff --git a/src/JsonSchemaEditor/index.tsx b/src/JsonSchemaEditor/index.tsx index 6f032db..5aa3555 100644 --- a/src/JsonSchemaEditor/index.tsx +++ b/src/JsonSchemaEditor/index.tsx @@ -1,228 +1,249 @@ import { message } from 'antd'; import _ from 'lodash'; -import React, { useEffect, useState } from 'react'; +import React, { + forwardRef, + useEffect, + useImperativeHandle, + useState, +} from 'react'; import SchemaItem from './SchemaItem'; import { JSONSchema7, SchemaEditorProps } from './types'; import { getDefaultSchema, inferSchema } from './utils'; -function JsonSchemaEditor(props: SchemaEditorProps) { - // const [schema, setSchema] = useState({ - // type: 'object', properties: { - // 'objectP': {type: 'object', properties: {'o1numberP': {type: 'number'}}}, - // 'numberP': {type: 'number'}, - // 'booleanP': {type: 'boolean'}, - // 'arrayString': {type: 'array', items: {type: 'string'}}, - // 'arrayObject': {type: 'array', items: {type: 'object', properties: {'arrayObjStr': {type: 'string'}}}}, - // 'integerP': {type: 'integer'}, - // 'stringP': {type: 'string'}, - // } - // }) - const [messageApi, contextHolder] = message.useMessage(); +export interface JsonSchemaEditorHandle { + changeSchema: (namePath: number[], value: any, propertyName?: string) => void; +} - function initSchema(data: string | undefined | JSONSchema7): JSONSchema7 { - const defaultSchema: JSONSchema7 = { - type: 'object', - properties: { - field: { type: 'string' }, - }, - }; - if (!data) { - return defaultSchema; - } - switch (typeof data) { - case 'string': - try { - return inferSchema(JSON.parse(data)); - } catch (e) { - messageApi.warning('初始化数据不是 Json 字符串,无法生成 JsonSchema'); - return defaultSchema; - } - case 'object': - return data; +const JsonSchemaEditor = forwardRef( + (props, ref) => { + const [messageApi, contextHolder] = message.useMessage(); + + function initSchema(data: string | undefined | JSONSchema7): JSONSchema7 { + const defaultSchema: JSONSchema7 = { + type: 'object', + properties: { + field: { type: 'string' }, + }, + }; + if (!data) { + return defaultSchema; + } + switch (typeof data) { + case 'string': + try { + return inferSchema(JSON.parse(data)); + } catch (e) { + messageApi.warning( + '初始化数据不是 Json 字符串,无法生成 JsonSchema', + ); + return defaultSchema; + } + case 'object': + return data; + } } - } - const [schema, setSchema] = useState(initSchema(props.data)); - const [fieldCount, setFieldCount] = useState(0); + // const [schema, setSchema] = useState({ + // type: 'object', properties: { + // 'objectP': {type: 'object', properties: {'o1numberP': {type: 'number'}}}, + // 'numberP': {type: 'number'}, + // 'booleanP': {type: 'boolean'}, + // 'arrayString': {type: 'array', items: {type: 'string'}}, + // 'arrayObject': {type: 'array', items: {type: 'object', properties: {'arrayObjStr': {type: 'string'}}}}, + // 'integerP': {type: 'integer'}, + // 'stringP': {type: 'string'}, + // } + // }) - useEffect(() => { - if (props.onSchemaChange) { - props.onSchemaChange(schema); - } - }, [schema]); + const [schema, setSchema] = useState(initSchema(props.data)); + const [fieldCount, setFieldCount] = useState(0); - function changeSchema(namePath: number[], value: any, propertyName: string) { - // console.log("changeSchema", namePath, value, propertyName); - if (namePath.length === 0) { - setSchema(value); - return; - } - let schemaClone = _.cloneDeep(schema); - let current: any = schemaClone; - for (let i = 0; i < namePath.length - 1; i++) { - const key = Object.keys(current)[namePath[i]]; - if (!current[key]) { - current[key] = {}; - } - current = current[key]; - } - const lastKey = namePath[namePath.length - 1]; - const lastKeyActual = Object.keys(current)[lastKey]; - if (lastKey === -1) { - if (typeof value === 'undefined') { - return; + useEffect(() => { + if (props.onSchemaChange) { + props.onSchemaChange(schema); } - current[propertyName] = value; - } else { - if (current[lastKeyActual] === value) { + }, [schema]); + + const changeSchema = ( + namePath: number[], + value: any, + propertyName?: string, + ) => { + // console.log("changeSchema", namePath, value, propertyName); + if (namePath.length === 0) { + setSchema(value); return; } - current[lastKeyActual] = value; - } - setSchema(schemaClone); - } + let schemaClone = _.cloneDeep(schema); + let current: any = schemaClone; + for (let i = 0; i < namePath.length - 1; i++) { + const key = Object.keys(current)[namePath[i]]; + if (!current[key]) { + current[key] = {}; + } + current = current[key]; + } + const lastKey = namePath[namePath.length - 1]; + const lastKeyActual = Object.keys(current)[lastKey]; + if (lastKey === -1) { + if (typeof value === 'undefined' || !propertyName) { + return; + } + current[propertyName] = value; + } else { + if (current[lastKeyActual] === value) { + return; + } + current[lastKeyActual] = value; + } + setSchema(schemaClone); + }; - function renameProperty(path: number[], newKey: string | number): void { - let schemaClone = _.cloneDeep(schema); - let current: any = schemaClone; - let parent: any = null; - let lastKey: string | number = ''; - for (let i = 0; i < path.length - 1; i++) { + function renameProperty(path: number[], newKey: string | number): void { + let schemaClone = _.cloneDeep(schema); + let current: any = schemaClone; + let parent: any = null; + let lastKey: string | number = ''; + for (let i = 0; i < path.length - 1; i++) { + const keys = Object.keys(current); + let index: number; + if (typeof path[i] === 'number') { + index = path[i] as number; + } else { + index = keys.indexOf(String(path[i])); + } + if (index < 0 || index >= keys.length) { + console.error(`Path not found: ${path.slice(0, i + 1).join('.')}`); + return; + } + parent = current; + lastKey = keys[index]; + current = current[lastKey]; + } + const oldKeyIndex = path[path.length - 1]; const keys = Object.keys(current); - let index: number; - if (typeof path[i] === 'number') { - index = path[i] as number; - } else { - index = keys.indexOf(String(path[i])); + const oldKey = keys[oldKeyIndex]; + if (oldKey === newKey) { + return; } - if (index < 0 || index >= keys.length) { - console.error(`Path not found: ${path.slice(0, i + 1).join('.')}`); + if (current.hasOwnProperty(oldKey)) { + parent[lastKey] = Object.fromEntries( + Object.entries(current).map(([key, value]) => { + if (key === oldKey) { + return [newKey, value]; + } + return [key, value]; + }), + ); + } else { + console.error(`Key not found: ${oldKey}`); return; } - parent = current; - lastKey = keys[index]; - current = current[lastKey]; - } - const oldKeyIndex = path[path.length - 1]; - const keys = Object.keys(current); - const oldKey = keys[oldKeyIndex]; - if (oldKey === newKey) { - return; + setSchema(schemaClone); } - if (current.hasOwnProperty(oldKey)) { - parent[lastKey] = Object.fromEntries( - Object.entries(current).map(([key, value]) => { - if (key === oldKey) { - return [newKey, value]; - } - return [key, value]; - }), - ); - } else { - console.error(`Key not found: ${oldKey}`); - return; - } - setSchema(schemaClone); - } - function updateRequired(target: any, property: string, remove: boolean) { - if (!target.required) { - target.required = []; - } - const index = target.required.indexOf(property); - if (remove) { - if (index !== -1) { - target.required.splice(index, 1); + function updateRequired(target: any, property: string, remove: boolean) { + if (!target.required) { + target.required = []; } - } else { - if (index === -1) { - target.required.push(property); + const index = target.required.indexOf(property); + if (remove) { + if (index !== -1) { + target.required.splice(index, 1); + } + } else { + if (index === -1) { + target.required.push(property); + } + } + if (target.required.length === 0) { + delete target.required; } } - if (target.required.length === 0) { - delete target.required; + + function updateRequiredProperty( + path: number[], + requiredProperty: string, + removed: boolean, + ) { + // console.log("updateRequiredProperty", path, requiredProperty, removed); + let schemaClone = _.cloneDeep(schema); + let current: any = schemaClone; + for (let i = 0; i < path.length; i++) { + const index = path[i]; + const keys = Object.keys(current); + if (typeof current[keys[index]] === 'undefined') { + current[keys[index]] = {}; + } + current = current[keys[index]]; + } + updateRequired(current, requiredProperty, removed); + setSchema(schemaClone); } - } - function updateRequiredProperty( - path: number[], - requiredProperty: string, - removed: boolean, - ) { - // console.log("updateRequiredProperty", path, requiredProperty, removed); - let schemaClone = _.cloneDeep(schema); - let current: any = schemaClone; - for (let i = 0; i < path.length; i++) { - const index = path[i]; - const keys = Object.keys(current); - if (typeof current[keys[index]] === 'undefined') { - current[keys[index]] = {}; + function removeProperty(path: number[]) { + let schemaClone = _.cloneDeep(schema); + let current: any = schemaClone; + let pre: any = schemaClone; + for (let i = 0; i < path.length - 1; i++) { + if (current !== undefined && current !== null) { + pre = current; + current = current[Object.keys(current)[path[i]]]; + } else { + console.error('移除的路径无效', path); + return; // 如果路径无效,则直接返回 + } + } + let finalKey = Object.keys(current)[path[path.length - 1]]; + updateRequired(pre, finalKey, true); + if ( + current && + typeof current === 'object' && + current.hasOwnProperty(finalKey) + ) { + delete current[finalKey]; } - current = current[keys[index]]; + setSchema(schemaClone); } - updateRequired(current, requiredProperty, removed); - setSchema(schemaClone); - } - function removeProperty(path: number[]) { - let schemaClone = _.cloneDeep(schema); - let current: any = schemaClone; - let pre: any = schemaClone; - for (let i = 0; i < path.length - 1; i++) { - if (current !== undefined && current !== null) { - pre = current; - current = current[Object.keys(current)[path[i]]]; + function addProperty(namePath: number[], isChild: boolean) { + // console.log(namePath, isChild, 'addProperty'); + let schemaClone = _.cloneDeep(schema); + let current: any = schemaClone; + for (let i = 0; i < namePath.length - (isChild ? 0 : 1); i++) { + const key = Object.keys(current)[namePath[i]]; + if (!current[key]) { + current[key] = {}; + } + current = current[key]; + } + const newSchema = getDefaultSchema('string'); + if (isChild) { + current['properties'][`field_${fieldCount}`] = newSchema; } else { - console.error('移除的路径无效', path); - return; // 如果路径无效,则直接返回 + current[`field_${fieldCount}`] = newSchema; } + setFieldCount(fieldCount + 1); + setSchema(schemaClone); } - let finalKey = Object.keys(current)[path[path.length - 1]]; - updateRequired(pre, finalKey, true); - if ( - current && - typeof current === 'object' && - current.hasOwnProperty(finalKey) - ) { - delete current[finalKey]; - } - setSchema(schemaClone); - } - function addProperty(namePath: number[], isChild: boolean) { - // console.log(namePath, isChild, 'addProperty'); - let schemaClone = _.cloneDeep(schema); - let current: any = schemaClone; - for (let i = 0; i < namePath.length - (isChild ? 0 : 1); i++) { - const key = Object.keys(current)[namePath[i]]; - if (!current[key]) { - current[key] = {}; - } - current = current[key]; - } - const newSchema = getDefaultSchema('string'); - if (isChild) { - current['properties'][`field_${fieldCount}`] = newSchema; - } else { - current[`field_${fieldCount}`] = newSchema; - } - setFieldCount(fieldCount + 1); - setSchema(schemaClone); - } + useImperativeHandle(ref, () => ({ changeSchema })); - return ( -
- {contextHolder} - -
- ); -} + return ( +
+ {contextHolder} + +
+ ); + }, +); export default JsonSchemaEditor; diff --git a/src/JsonSchemaEditor/types.ts b/src/JsonSchemaEditor/types.ts index 67f3ab6..1eceb52 100644 --- a/src/JsonSchemaEditor/types.ts +++ b/src/JsonSchemaEditor/types.ts @@ -1,6 +1,11 @@ export type SchemaEditorProps = { data?: JSONSchema7 | undefined | string; onSchemaChange?: (schema: JSONSchema7) => void; + handleAdvancedSettingClick?: ( + namePath: number[], + schema: JSONSchema7, + propertyName?: string, + ) => boolean; }; //==================================================================================================