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
This commit is contained in:
Zeke Zhang 2025-03-26 14:37:01 +08:00 committed by GitHub
parent 27c2538d40
commit c80fbc3063
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 128 additions and 81 deletions

View File

@ -48,6 +48,7 @@ interface INocoBaseRecursionFieldProps extends IRecursionFieldProps {
* Whether to use Formily Field class - performance will be reduced but provides better compatibility with Formily * Whether to use Formily Field class - performance will be reduced but provides better compatibility with Formily
*/ */
isUseFormilyField?: boolean; isUseFormilyField?: boolean;
parentSchema?: Schema;
} }
const CollectionFieldUISchemaContext = React.createContext<CollectionFieldOptions>({}); const CollectionFieldUISchemaContext = React.createContext<CollectionFieldOptions>({});
@ -266,6 +267,7 @@ export const NocoBaseRecursionField: ReactFC<INocoBaseRecursionFieldProps> = Rea
values, values,
isUseFormilyField = true, isUseFormilyField = true,
uiSchema, uiSchema,
parentSchema,
} = props; } = props;
const basePath = useBasePath(props); const basePath = useBasePath(props);
const newFieldSchemaRef = useRef(null); const newFieldSchemaRef = useRef(null);
@ -279,6 +281,14 @@ export const NocoBaseRecursionField: ReactFC<INocoBaseRecursionFieldProps> = Rea
const fieldSchema: Schema = newFieldSchemaRef.current || oldFieldSchema; 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 refresh = useCallback(() => {
const parent = fieldSchema.parent; const parent = fieldSchema.parent;
newFieldSchemaRef.current = new Schema(fieldSchema.toJSON(), parent); newFieldSchemaRef.current = new Schema(fieldSchema.toJSON(), parent);

View File

@ -273,31 +273,43 @@ const InternalPagePopups = (props: { paramsList?: PopupParams[] }) => {
); );
}); });
const schemas = await Promise.all(waitList); const schemas = await Promise.all(waitList);
const clonedSchemas = schemas.map((schema, index) => { const clonedSchemas = await Promise.all(
if (_.isEmpty(schema)) { schemas.map(async (schema, index) => {
return get404Schema(); 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);
} }
}
// Using toJSON for deep clone, faster than lodash's cloneDeep const params = popupParams[index];
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 (params.puid) {
if (value?.['x-component'] === 'Tabs') { const popupSchema = findSchemaByUid(params.puid, fieldSchema?.root);
return value; 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) => { popupPropsRef.current = clonedSchemas.map((schema, index, items) => {
const schemaContext = getPopupContextFromActionOrAssociationFieldSchema(schema); const schemaContext = getPopupContextFromActionOrAssociationFieldSchema(schema);
let hidden = false; let hidden = false;

View File

@ -8,7 +8,7 @@
*/ */
import { createForm } from '@formily/core'; import { createForm } from '@formily/core';
import { Schema } from '@formily/react'; import { Schema, useFieldSchema } from '@formily/react';
import { Spin } from 'antd'; import { Spin } from 'antd';
import React, { memo, useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import { useRemoteCollectionManagerLoading } from '../../collection-manager/CollectionManagerProvider'; import { useRemoteCollectionManagerLoading } from '../../collection-manager/CollectionManagerProvider';
@ -62,6 +62,7 @@ const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) =>
}); });
const NotFoundComponent = useComponent(NotFoundPage); const NotFoundComponent = useComponent(NotFoundPage);
const collectionManagerLoading = useRemoteCollectionManagerLoading(); const collectionManagerLoading = useRemoteCollectionManagerLoading();
const parentSchema = useFieldSchema();
if (collectionManagerLoading || loading || hidden) { if (collectionManagerLoading || loading || hidden) {
return <Spin style={{ width: '100%', marginTop: 20 }} delay={LOADING_DELAY} />; return <Spin style={{ width: '100%', marginTop: 20 }} delay={LOADING_DELAY} />;
@ -73,10 +74,20 @@ const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) =>
} }
return noForm ? ( return noForm ? (
<SchemaComponent components={components} scope={scope} schema={schemaTransform(schema || {})} /> <SchemaComponent
components={components}
scope={scope}
schema={schemaTransform(schema || {})}
parentSchema={parentSchema}
/>
) : ( ) : (
<FormProvider form={form}> <FormProvider form={form}>
<SchemaComponent components={components} scope={scope} schema={schemaTransform(schema || {})} /> <SchemaComponent
components={components}
scope={scope}
schema={schemaTransform(schema || {})}
parentSchema={parentSchema}
/>
</FormProvider> </FormProvider>
); );
}; };

View File

@ -28,6 +28,7 @@ function toSchema(schema?: any) {
properties: { properties: {
[schema.name]: schema, [schema.name]: schema,
}, },
name: `p_${schema.name}`,
}); });
} }
return new Schema(schema); return new Schema(schema);
@ -52,58 +53,65 @@ interface DistributedProps {
*/ */
export const SchemaComponentOnChangeContext = createContext<SchemaComponentOnChange>({ onChange: _.noop }); export const SchemaComponentOnChangeContext = createContext<SchemaComponentOnChange>({ onChange: _.noop });
const RecursionSchemaComponent = memo((props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps) => { const RecursionSchemaComponent = memo(
const { components, scope, schema: _schema, distributed, onChange: _onChange, ...others } = props; (props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps & { parentSchema?: Schema }) => {
const ctx = useContext(SchemaComponentContext); const { components, scope, schema: _schema, distributed, onChange: _onChange, parentSchema, ...others } = props;
const schema = useMemo(() => toSchema(_schema), [_schema]); const ctx = useContext(SchemaComponentContext);
const value = useMemo( const schema = useMemo(() => toSchema(_schema), [_schema]);
() => ({ const value = useMemo(
...ctx, () => ({
distributed: ctx.distributed == false ? false : distributed, ...ctx,
/** distributed: ctx.distributed == false ? false : distributed,
* @deprecated /**
*/ * @deprecated
refresh: ctx.refresh || _.noop, */
}), refresh: ctx.refresh || _.noop,
[ctx, distributed], }),
); [ctx, distributed],
);
const { onChange: onChangeFromContext } = useContext(SchemaComponentOnChangeContext); const { onChange: onChangeFromContext } = useContext(SchemaComponentOnChangeContext);
const onChangeValue = useMemo( const onChangeValue = useMemo(
() => ({ () => ({
onChange: () => { onChange: () => {
_onChange?.(schema); _onChange?.(schema);
onChangeFromContext?.(); onChangeFromContext?.();
}, },
}), }),
[_onChange, onChangeFromContext, schema], [_onChange, onChangeFromContext, schema],
); );
return ( return (
<SchemaComponentOnChangeContext.Provider value={onChangeValue}> <SchemaComponentOnChangeContext.Provider value={onChangeValue}>
<SchemaComponentContext.Provider value={value}> <SchemaComponentContext.Provider value={value}>
<SchemaComponentOptions inherit components={components} scope={scope}> <SchemaComponentOptions inherit components={components} scope={scope}>
<NocoBaseRecursionField {...others} schema={schema} isUseFormilyField /> <NocoBaseRecursionField {...others} schema={schema} isUseFormilyField parentSchema={parentSchema} />
</SchemaComponentOptions> </SchemaComponentOptions>
</SchemaComponentContext.Provider> </SchemaComponentContext.Provider>
</SchemaComponentOnChangeContext.Provider> </SchemaComponentOnChangeContext.Provider>
); );
}); },
);
RecursionSchemaComponent.displayName = 'RecursionSchemaComponent'; RecursionSchemaComponent.displayName = 'RecursionSchemaComponent';
const MemoizedSchemaComponent = memo((props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps) => { const MemoizedSchemaComponent = memo(
const { schema, ...others } = props; (props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps & { parentSchema?: Schema }) => {
const s = useMemoizedSchema(schema); const { schema, parentSchema, ...others } = props;
return <RecursionSchemaComponent {...others} schema={s} />; const s = useMemoizedSchema(schema);
}); return <RecursionSchemaComponent {...others} schema={s} parentSchema={parentSchema} />;
},
);
MemoizedSchemaComponent.displayName = 'MemoizedSchemaComponent'; MemoizedSchemaComponent.displayName = 'MemoizedSchemaComponent';
export const SchemaComponent = memo( export const SchemaComponent = memo(
( (
props: (ISchemaFieldProps | IRecursionFieldProps) & { memoized?: boolean } & SchemaComponentOnChange & props: (ISchemaFieldProps | IRecursionFieldProps) & {
memoized?: boolean;
parentSchema?: Schema;
} & SchemaComponentOnChange &
DistributedProps, DistributedProps,
) => { ) => {
const { memoized, ...others } = props; const { memoized, ...others } = props;

View File

@ -135,20 +135,22 @@ export const KanbanCard: any = () => {
return ( return (
<> <>
<Card onClick={handleCardClick} bordered={false} hoverable style={cardStyle} className={cardCss}> <PopupContextProvider>
<DndContext onDragStart={onDragStart} onDragEnd={onDragEnd}> <Card onClick={handleCardClick} bordered={false} hoverable style={cardStyle} className={cardCss}>
<FormLayout <DndContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
layout={layout} <FormLayout
labelAlign={labelAlign} layout={layout}
labelWidth={layout === 'horizontal' ? labelWidth : null} labelAlign={labelAlign}
labelWrap={labelWrap} labelWidth={layout === 'horizontal' ? labelWidth : null}
> labelWrap={labelWrap}
<FormProvider form={form}> >
<MemorizedRecursionField schema={fieldSchema} onlyRenderProperties /> <FormProvider form={form}>
</FormProvider> <MemorizedRecursionField schema={fieldSchema} onlyRenderProperties />
</FormLayout> </FormProvider>
</DndContext> </FormLayout>
</Card> </DndContext>
</Card>
</PopupContextProvider>
<PopupContextProvider visible={visible} setVisible={setVisible}> <PopupContextProvider visible={visible} setVisible={setVisible}>
<VariablePopupRecordProvider recordData={recordData} collection={collection}> <VariablePopupRecordProvider recordData={recordData} collection={collection}>
<MemorizedRecursionField schema={wrappedPopupSchema} /> <MemorizedRecursionField schema={wrappedPopupSchema} />
@ -163,8 +165,12 @@ function getPopupSchemaFromParent(fieldSchema: Schema) {
return fieldSchema.parent.properties.cardViewer.properties.drawer; return fieldSchema.parent.properties.cardViewer.properties.drawer;
} }
const cardSchema = findSchemaByUid(fieldSchema['x-uid'], fieldSchema.root); try {
return cardSchema.parent.properties.cardViewer.properties.drawer; 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 }) { function findSchemaByUid(uid: string, rootSchema: Schema, resultRef: { value: Schema } = { value: null }) {