From c80fbc306365a4cea1ba125c327540bf8c3c01eb Mon Sep 17 00:00:00 2001 From: Zeke Zhang <958414905@qq.com> Date: Wed, 26 Mar 2025 14:37:01 +0800 Subject: [PATCH] fix(kanban): unable to open popup (#6535) * fix: handle potential errors when retrieving card schema in Kanban * fix(kanban): unable to open popup * fix(Link): cannot use 'current user' variable * fix(PagePopups): improve schema fetching and cloning logic for popups * fix(Kanban): clicking cards repeatedly opens multiple popups --- .../src/formily/NocoBaseRecursionField.tsx | 10 +++ .../schema-component/antd/page/PagePopups.tsx | 54 ++++++----- .../core/RemoteSchemaComponent.tsx | 17 +++- .../schema-component/core/SchemaComponent.tsx | 90 ++++++++++--------- .../plugin-kanban/src/client/Kanban.Card.tsx | 38 ++++---- 5 files changed, 128 insertions(+), 81 deletions(-) diff --git a/packages/core/client/src/formily/NocoBaseRecursionField.tsx b/packages/core/client/src/formily/NocoBaseRecursionField.tsx index c2c75af9bb..cf0d00c880 100644 --- a/packages/core/client/src/formily/NocoBaseRecursionField.tsx +++ b/packages/core/client/src/formily/NocoBaseRecursionField.tsx @@ -48,6 +48,7 @@ interface INocoBaseRecursionFieldProps extends IRecursionFieldProps { * Whether to use Formily Field class - performance will be reduced but provides better compatibility with Formily */ isUseFormilyField?: boolean; + parentSchema?: Schema; } const CollectionFieldUISchemaContext = React.createContext({}); @@ -266,6 +267,7 @@ export const NocoBaseRecursionField: ReactFC = Rea values, isUseFormilyField = true, uiSchema, + parentSchema, } = props; const basePath = useBasePath(props); const newFieldSchemaRef = useRef(null); @@ -279,6 +281,14 @@ export const NocoBaseRecursionField: ReactFC = Rea const fieldSchema: Schema = newFieldSchemaRef.current || oldFieldSchema; + // Establish connection with the Schema tree + if (!fieldSchema.parent && parentSchema) { + fieldSchema.parent = parentSchema; + if (!fieldSchema.parent?.properties?.[fieldSchema.name] && fieldSchema.name) { + _.set(fieldSchema.parent, `properties.${fieldSchema.name}`, fieldSchema); + } + } + const refresh = useCallback(() => { const parent = fieldSchema.parent; newFieldSchemaRef.current = new Schema(fieldSchema.toJSON(), parent); diff --git a/packages/core/client/src/schema-component/antd/page/PagePopups.tsx b/packages/core/client/src/schema-component/antd/page/PagePopups.tsx index faef6cce0c..818cf75460 100644 --- a/packages/core/client/src/schema-component/antd/page/PagePopups.tsx +++ b/packages/core/client/src/schema-component/antd/page/PagePopups.tsx @@ -273,31 +273,43 @@ const InternalPagePopups = (props: { paramsList?: PopupParams[] }) => { ); }); const schemas = await Promise.all(waitList); - const clonedSchemas = schemas.map((schema, index) => { - if (_.isEmpty(schema)) { - return get404Schema(); - } - - const params = popupParams[index]; - - if (params.puid) { - const popupSchema = findSchemaByUid(params.puid, fieldSchema?.root); - if (popupSchema) { - savePopupSchemaToSchema(_.omit(popupSchema, 'parent'), schema); + const clonedSchemas = await Promise.all( + schemas.map(async (schema, index) => { + if (_.isEmpty(schema)) { + return get404Schema(); } - } - // Using toJSON for deep clone, faster than lodash's cloneDeep - const result = _.cloneDeepWith(_.omit(schema, 'parent'), (value) => { - // If we clone the Tabs component, it will cause the configuration to be lost when reopening the popup after modifying its settings - if (value?.['x-component'] === 'Tabs') { - return value; + const params = popupParams[index]; + + if (params.puid) { + const popupSchema = findSchemaByUid(params.puid, fieldSchema?.root); + if (popupSchema) { + savePopupSchemaToSchema(_.omit(popupSchema, 'parent'), schema); + } else { + // 当本地找不到 popupSchema 时,通过接口请求 puid 对应的 schema + try { + const remoteSchema = await requestSchema(params.puid); + if (remoteSchema) { + savePopupSchemaToSchema(remoteSchema, schema); + } + } catch (error) { + console.error('Failed to fetch schema for puid:', params.puid, error); + } + } } - }); - result['x-read-pretty'] = true; - return result; - }); + // Using toJSON for deep clone, faster than lodash's cloneDeep + const result = _.cloneDeepWith(_.omit(schema, 'parent'), (value) => { + // If we clone the Tabs component, it will cause the configuration to be lost when reopening the popup after modifying its settings + if (value?.['x-component'] === 'Tabs') { + return value; + } + }); + result['x-read-pretty'] = true; + + return result; + }), + ); popupPropsRef.current = clonedSchemas.map((schema, index, items) => { const schemaContext = getPopupContextFromActionOrAssociationFieldSchema(schema); let hidden = false; diff --git a/packages/core/client/src/schema-component/core/RemoteSchemaComponent.tsx b/packages/core/client/src/schema-component/core/RemoteSchemaComponent.tsx index 356705c31b..f220126041 100644 --- a/packages/core/client/src/schema-component/core/RemoteSchemaComponent.tsx +++ b/packages/core/client/src/schema-component/core/RemoteSchemaComponent.tsx @@ -8,7 +8,7 @@ */ import { createForm } from '@formily/core'; -import { Schema } from '@formily/react'; +import { Schema, useFieldSchema } from '@formily/react'; import { Spin } from 'antd'; import React, { memo, useMemo } from 'react'; import { useRemoteCollectionManagerLoading } from '../../collection-manager/CollectionManagerProvider'; @@ -62,6 +62,7 @@ const RequestSchemaComponent: React.FC = (props) => }); const NotFoundComponent = useComponent(NotFoundPage); const collectionManagerLoading = useRemoteCollectionManagerLoading(); + const parentSchema = useFieldSchema(); if (collectionManagerLoading || loading || hidden) { return ; @@ -73,10 +74,20 @@ const RequestSchemaComponent: React.FC = (props) => } return noForm ? ( - + ) : ( - + ); }; diff --git a/packages/core/client/src/schema-component/core/SchemaComponent.tsx b/packages/core/client/src/schema-component/core/SchemaComponent.tsx index 5a51340ace..2443eb5c43 100644 --- a/packages/core/client/src/schema-component/core/SchemaComponent.tsx +++ b/packages/core/client/src/schema-component/core/SchemaComponent.tsx @@ -28,6 +28,7 @@ function toSchema(schema?: any) { properties: { [schema.name]: schema, }, + name: `p_${schema.name}`, }); } return new Schema(schema); @@ -52,58 +53,65 @@ interface DistributedProps { */ export const SchemaComponentOnChangeContext = createContext({ onChange: _.noop }); -const RecursionSchemaComponent = memo((props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps) => { - const { components, scope, schema: _schema, distributed, onChange: _onChange, ...others } = props; - const ctx = useContext(SchemaComponentContext); - const schema = useMemo(() => toSchema(_schema), [_schema]); - const value = useMemo( - () => ({ - ...ctx, - distributed: ctx.distributed == false ? false : distributed, - /** - * @deprecated - */ - refresh: ctx.refresh || _.noop, - }), - [ctx, distributed], - ); +const RecursionSchemaComponent = memo( + (props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps & { parentSchema?: Schema }) => { + const { components, scope, schema: _schema, distributed, onChange: _onChange, parentSchema, ...others } = props; + const ctx = useContext(SchemaComponentContext); + const schema = useMemo(() => toSchema(_schema), [_schema]); + const value = useMemo( + () => ({ + ...ctx, + distributed: ctx.distributed == false ? false : distributed, + /** + * @deprecated + */ + refresh: ctx.refresh || _.noop, + }), + [ctx, distributed], + ); - const { onChange: onChangeFromContext } = useContext(SchemaComponentOnChangeContext); + const { onChange: onChangeFromContext } = useContext(SchemaComponentOnChangeContext); - const onChangeValue = useMemo( - () => ({ - onChange: () => { - _onChange?.(schema); - onChangeFromContext?.(); - }, - }), - [_onChange, onChangeFromContext, schema], - ); + const onChangeValue = useMemo( + () => ({ + onChange: () => { + _onChange?.(schema); + onChangeFromContext?.(); + }, + }), + [_onChange, onChangeFromContext, schema], + ); - return ( - - - - - - - - ); -}); + return ( + + + + + + + + ); + }, +); RecursionSchemaComponent.displayName = 'RecursionSchemaComponent'; -const MemoizedSchemaComponent = memo((props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps) => { - const { schema, ...others } = props; - const s = useMemoizedSchema(schema); - return ; -}); +const MemoizedSchemaComponent = memo( + (props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps & { parentSchema?: Schema }) => { + const { schema, parentSchema, ...others } = props; + const s = useMemoizedSchema(schema); + return ; + }, +); MemoizedSchemaComponent.displayName = 'MemoizedSchemaComponent'; export const SchemaComponent = memo( ( - props: (ISchemaFieldProps | IRecursionFieldProps) & { memoized?: boolean } & SchemaComponentOnChange & + props: (ISchemaFieldProps | IRecursionFieldProps) & { + memoized?: boolean; + parentSchema?: Schema; + } & SchemaComponentOnChange & DistributedProps, ) => { const { memoized, ...others } = props; diff --git a/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Card.tsx b/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Card.tsx index d554caef17..f534d649cd 100644 --- a/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Card.tsx +++ b/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Card.tsx @@ -135,20 +135,22 @@ export const KanbanCard: any = () => { return ( <> - - - - - - - - - + + + + + + + + + + + @@ -163,8 +165,12 @@ function getPopupSchemaFromParent(fieldSchema: Schema) { return fieldSchema.parent.properties.cardViewer.properties.drawer; } - const cardSchema = findSchemaByUid(fieldSchema['x-uid'], fieldSchema.root); - return cardSchema.parent.properties.cardViewer.properties.drawer; + try { + const cardSchema = findSchemaByUid(fieldSchema['x-uid'], fieldSchema.root); + return cardSchema.parent.properties.cardViewer.properties.drawer; + } catch (e) { + console.warn(e); + } } function findSchemaByUid(uid: string, rootSchema: Schema, resultRef: { value: Schema } = { value: null }) {