feat(variable): add current parent record (#2857)

* feat(variable): add currentParentRecord

* chore: resolve table actions

* chore: get collection name of parent

* chore: table block and details block

* chore: list block

* chore: grid card block

* chore: calendar

* fix: bulk button

* refactor: use useMemo

* fix: fix form block
This commit is contained in:
被雨水过滤的空气-Rain 2023-10-19 22:55:05 +08:00 committed by GitHub
parent 010c286f7c
commit e05a30380a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 857 additions and 621 deletions

View File

@ -5,7 +5,7 @@ import { useRequest } from 'ahooks';
import { Col, Row } from 'antd';
import merge from 'deepmerge';
import template from 'lodash/template';
import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import {
TableFieldResource,
@ -285,7 +285,7 @@ export const RenderChildrenWithAssociationFilter: React.FC<any> = (props) => {
export const BlockProvider = (props) => {
const { collection, association } = props;
const resource = useResource(props);
const params = { ...props.params };
const params = useMemo(() => ({ ...props.params }), [props.params]);
const { appends, updateAssociationValues } = useAssociationNames();
if (!Object.keys(params).includes('appends')) {
params['appends'] = appends;

View File

@ -1,8 +1,11 @@
import { ArrayField } from '@formily/core';
import { useField } from '@formily/react';
import _ from 'lodash';
import React, { createContext, useContext, useEffect } from 'react';
import { useRecord } from '../record-provider';
import { FixedBlockWrapper } from '../schema-component';
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
import { useParsedFilter } from './hooks';
export const CalendarBlockContext = createContext<any>({});
@ -10,6 +13,18 @@ const InternalCalendarBlockProvider = (props) => {
const { fieldNames, showLunar } = props;
const field = useField();
const { resource, service } = useBlockRequestContext();
const record = useRecord();
const { filter } = useParsedFilter({
filterOption: service?.params?.[0]?.filter,
currentRecord: { __parent: record, __collectionName: props.collection },
});
useEffect(() => {
if (!_.isEmpty(filter)) {
service?.run({ ...service?.params?.[0], filter });
}
}, [JSON.stringify(filter)]);
// if (service.loading) {
// return <Spin />;
// }

View File

@ -1,9 +1,11 @@
import { createForm } from '@formily/core';
import { useField } from '@formily/react';
import { Spin } from 'antd';
import _ from 'lodash';
import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { RecordProvider } from '../record-provider';
import { RecordProvider, useRecord } from '../record-provider';
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
import { useParsedFilter } from './hooks';
export const DetailsBlockContext = createContext<any>({});
@ -18,21 +20,36 @@ const InternalDetailsBlockProvider = (props) => {
[],
);
const { resource, service } = useBlockRequestContext();
if (service.loading && !field.loaded) {
return <Spin />;
}
field.loaded = true;
return (
<DetailsBlockContext.Provider
value={{
const record = useRecord();
const currentRecord = service?.data?.data?.[0] || {};
const detailsBLockValue = useMemo(() => {
return {
action,
form,
field,
service,
resource,
}}
>
<RecordProvider record={service?.data?.data?.[0] || {}}>{props.children}</RecordProvider>
};
}, [action, field, form, resource, service]);
const { filter } = useParsedFilter({
filterOption: service?.params?.[0]?.filter,
currentRecord: { ...currentRecord, __parent: record, __collectionName: props.collection },
});
useEffect(() => {
if (!_.isEmpty(filter)) {
service?.run({ ...service?.params?.[0], filter });
}
}, [JSON.stringify(filter)]);
if (service.loading && !field.loaded) {
return <Spin />;
}
field.loaded = true;
return (
<DetailsBlockContext.Provider value={detailsBLockValue}>
<RecordProvider record={currentRecord}>{props.children}</RecordProvider>
</DetailsBlockContext.Provider>
);
};
@ -53,9 +70,12 @@ export const useDetailsBlockProps = () => {
const ctx = useDetailsBlockContext();
useEffect(() => {
if (!ctx.service.loading) {
ctx.form.reset().then(() => {
ctx.form
.reset()
.then(() => {
ctx.form.setValues(ctx.service?.data?.data?.[0] || {});
});
})
.catch(console.error);
}
}, [ctx.service.loading]);
return {

View File

@ -1,7 +1,7 @@
import { createForm } from '@formily/core';
import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
import { Spin } from 'antd';
import { isEmpty } from 'lodash';
import _, { isEmpty } from 'lodash';
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
import { useCollection } from '../collection-manager';
import { RecordProvider, useRecord } from '../record-provider';
@ -13,7 +13,7 @@ import { FormActiveFieldsProvider } from './hooks';
export const FormBlockContext = createContext<any>({});
const InternalFormBlockProvider = (props) => {
const { action, readPretty, params } = props;
const { action, readPretty, params, association } = props;
const field = useField();
const form = useMemo(
() =>
@ -25,13 +25,8 @@ const InternalFormBlockProvider = (props) => {
const { resource, service, updateAssociationValues } = useBlockRequestContext();
const formBlockRef = useRef();
const record = useRecord();
if (service.loading && Object.keys(form?.initialValues)?.length === 0 && action) {
return <Spin />;
}
return (
<FormBlockContext.Provider
value={{
const formBlockValue = useMemo(() => {
return {
params,
action,
form,
@ -42,21 +37,39 @@ const InternalFormBlockProvider = (props) => {
resource,
updateAssociationValues,
formBlockRef,
}}
>
{readPretty ? (
<RecordProvider parent={isEmpty(record?.__parent) ? record : record?.__parent} record={service?.data?.data}>
};
}, [action, field, form, params, resource, service, updateAssociationValues]);
if (service.loading && Object.keys(form?.initialValues)?.length === 0 && action) {
return <Spin />;
}
let content = (
<div ref={formBlockRef}>
<RenderChildrenWithDataTemplates form={form} />
</div>
</RecordProvider>
) : (
<div ref={formBlockRef}>
<RenderChildrenWithDataTemplates form={form} />
</div>
)}
</FormBlockContext.Provider>
);
if (readPretty) {
content = (
<RecordProvider parent={isEmpty(record?.__parent) ? record : record?.__parent} record={service?.data?.data}>
{content}
</RecordProvider>
);
} else if (
formBlockValue.type === 'create' &&
// 点击关系表格区块的 Add new 按钮,在弹窗中新增的表单区块,是不需要重置 record 的。在这里用 record 是否为空来判断
!_.isEmpty(_.omit(record, ['__parent', '__collectionName'])) &&
// association 不为空,说明是关系区块
association
) {
content = (
<RecordProvider parent={record} record={{}}>
{content}
</RecordProvider>
);
}
return <FormBlockContext.Provider value={formBlockValue}>{content}</FormBlockContext.Provider>;
};
/**

View File

@ -3,10 +3,11 @@ import { FormContext, useField, useFieldSchema } from '@formily/react';
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { useCollectionManager } from '../collection-manager';
import { useFilterBlock } from '../filter-provider/FilterProvider';
import { useRecord } from '../record-provider';
import { FixedBlockWrapper, SchemaComponentOptions, removeNullCondition } from '../schema-component';
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
import { mergeFilter } from './SharedFilterProvider';
import { findFilterTargets } from './hooks';
import { findFilterTargets, useParsedFilter } from './hooks';
export const TableBlockContext = createContext<any>({});
export function getIdsWithChildren(nodes) {
@ -74,9 +75,11 @@ const InternalTableBlockProvider = (props: Props) => {
export const TableBlockProvider = (props) => {
const resourceName = props.resource;
const params = { ...props.params };
const params = useMemo(() => ({ ...props.params }), [props.params]);
const fieldSchema = useFieldSchema();
const { getCollection, getCollectionField } = useCollectionManager();
const record = useRecord();
const collection = getCollection(props.collection);
const { treeTable } = fieldSchema?.['x-decorator-props'] || {};
if (props.dragSort) {
@ -99,12 +102,22 @@ export const TableBlockProvider = (props) => {
}
}
const form = useMemo(() => createForm(), [treeTable]);
const { filter: parsedFilter } = useParsedFilter({
filterOption: params?.filter,
currentRecord: { __parent: record, __collectionName: props.collection },
});
const paramsWithFilter = useMemo(() => {
return {
...params,
filter: parsedFilter,
};
}, [parsedFilter, params]);
return (
<SchemaComponentOptions scope={{ treeTable }}>
<FormContext.Provider value={form}>
<BlockProvider data-testid="table-block" {...props} params={params} runWhenParamsChanged>
<InternalTableBlockProvider {...props} childrenColumnName={childrenColumnName} params={params} />
<BlockProvider data-testid="table-block" {...props} params={paramsWithFilter} runWhenParamsChanged>
<InternalTableBlockProvider {...props} childrenColumnName={childrenColumnName} params={paramsWithFilter} />
</BlockProvider>
</FormContext.Provider>
</SchemaComponentOptions>

View File

@ -26,6 +26,7 @@ import { mergeFilter } from '../SharedFilterProvider';
import { TableFieldResource } from '../TableFieldProvider';
export * from './useFormActiveFields';
export * from './useParsedFilter';
export const usePickActionProps = () => {
const form = useForm();
@ -587,8 +588,9 @@ export const useCustomizeBulkUpdateActionProps = () => {
const actionField = useField();
const { modal } = App.useApp();
const variables = useVariables();
const localVariables = useLocalVariables();
const record = useRecord();
const { name, getField } = useCollection();
const localVariables = useLocalVariables({ currentRecord: { __parent: record, __collectionName: name } });
return {
async onClick() {

View File

@ -0,0 +1,45 @@
import { reaction } from '@formily/reactive';
import { flatten } from '@nocobase/utils/client';
import _ from 'lodash';
import { useEffect, useState } from 'react';
import { useParseDataScopeFilter } from '../../schema-settings';
import { DEBOUNCE_WAIT } from '../../variables';
import { getPath } from '../../variables/utils/getPath';
import { isVariable } from '../../variables/utils/isVariable';
import { useFormBlockContext } from '../FormBlockProvider';
export function useParsedFilter({ filterOption, currentRecord }: { filterOption: any; currentRecord?: any }) {
const { parseFilter } = useParseDataScopeFilter({ currentRecord });
const [filter, setFilter] = useState({});
const { form } = useFormBlockContext();
useEffect(() => {
if (!filterOption) return;
const _run = async () => {
const result = await parseFilter(filterOption);
setFilter(result);
};
_run();
const run = _.debounce(_run, DEBOUNCE_WAIT);
reaction(() => {
// 这一步主要是为了使 reaction 能够收集到依赖
const flat = flatten(filterOption, {
breakOn({ key }) {
return key.startsWith('$') && key !== '$and' && key !== '$or';
},
transformValue(value) {
if (!isVariable(value)) {
return value;
}
const result = _.get({ $nForm: form?.values }, getPath(value));
return result;
},
});
return flat;
}, run);
}, [JSON.stringify(filterOption)]);
return { filter };
}

View File

@ -79,7 +79,10 @@ const getSchema = (schema: IField, record: any, compile, getContainer): ISchema
export const useValuesFromRecord = (options) => {
const record = useRecord();
const result = useRequest(
() => Promise.resolve({ data: { ...omit(record, ['__parent']), category: record?.category.map((v) => v.id) } }),
() =>
Promise.resolve({
data: { ...omit(record, ['__parent', '__collectionName']), category: record?.category.map((v) => v.id) },
}),
{
...options,
manual: true,

View File

@ -23,7 +23,7 @@ export const useCancelAction = () => {
export const useValuesFromRecord = (options) => {
const record = useRecord();
const result = useRequest(() => Promise.resolve({ data: omit(record, ['__parent']) }), {
const result = useRequest(() => Promise.resolve({ data: omit(record, ['__parent', '__collectionName']) }), {
...options,
manual: true,
});

View File

@ -61,6 +61,11 @@ export const FilterBlockProvider: React.FC = ({ children }) => {
return <FilterContext.Provider value={{ dataBlocks, setDataBlocks }}>{children}</FilterContext.Provider>;
};
/**
*
* @param param0
* @returns
*/
export const FilterBlockRecord = ({
children,
params,

View File

@ -619,6 +619,7 @@ export default {
"Dynamic value": "Dynamic value",
"Current user": "Current user",
"Current record": "Current record",
"Current parent record": "Current parent record",
"Current time": "Current time",
"System variables": "System variables",
"Date variables": "Date variables",

View File

@ -603,6 +603,7 @@ export default {
"Dynamic value": "Valor dinámico",
"Current user": "Usuario actual",
"Current record": "Registro actual",
"Current parent record": "Registro padre actual",
"Current time": "Hora actual",
"System variables": "Variables del sistema",
"Date variables": "Variables de fecha",

View File

@ -600,6 +600,7 @@ export default {
"Dynamic value": "Valeur dynamique",
"Current user": "Utilisateur actuel",
"Current record": "Enregistrement actuel",
"Current parent record": "Enregistrement parent actuel",
"Current time": "Heure actuelle",
"System variables": "Variables système",
"Date variables": "Variables de date",

View File

@ -502,6 +502,7 @@ export default {
"Dynamic value": "動的値",
"Current user": "現在のユーザー",
"Current record": "現在のレコード",
"Current parent record": "現在の親レコード",
"Popup close method": "ポップアップを閉じる方法",
"Automatic close": "自動で閉じる",
"Manually close": "手動で閉じる",

View File

@ -508,6 +508,7 @@ export default {
"Single select and radio fields can be used as the grouping field": "Campos de seleção única e de rádio podem ser usados como o campo de agrupamento",
"Tab name": "Nome da aba",
"Current record blocks": "Blocos de registro atual",
"Current parent record": "Registro pai atual",
"Popup message": "Mensagem pop-up",
"Delete role": "Excluir função",
"Role display name": "Nome de exibição da função",

View File

@ -439,6 +439,7 @@ export default {
"Dynamic value": "Динамическое значение",
"Current user": "Текущий пользователь",
"Current record": "Текущая запись",
"Current parent record": "Текущая родительская запись",
"Popup close method": "Метод закрытия всплывающего окна",
"Automatic close": "Автоматическое закрытие",
"Manually close": "Ручное закрытие",

View File

@ -438,6 +438,7 @@ export default {
"Dynamic value": "Dinamik değer",
"Current user": "Seçili kullanıcı",
"Current record": "Seçili kayıt",
"Current parent record": "Seçili üst kayıt",
"Popup close method": "Açılır pencere kapatma metodu",
"Automatic close": "Otomatik kapat",
"Manually close": "Manuel kapat",

View File

@ -620,6 +620,7 @@ export default {
"Dynamic value": "Динамічне значення",
"Current user": "Поточний користувач",
"Current record": "Поточний запис",
"Current parent record": "Поточний батьківський запис",
"Current time": "Поточний час",
"System variables": "Системні змінні",
"Date variables": "Змінні дати",

View File

@ -661,6 +661,7 @@ export default {
'Dynamic value': '动态值',
'Current user': '当前用户',
'Current record': '当前记录',
"Current parent record": "当前父记录",
'Current time': '当前时间',
Now: '现在',
'Popup close method': '弹窗关闭方式',

View File

@ -1,14 +1,17 @@
import React, { createContext, useContext } from 'react';
import { useCollection } from '../collection-manager';
import { useCurrentUserContext } from '../user';
export const RecordContext = createContext({});
export const RecordIndexContext = createContext(null);
export const RecordProvider: React.FC<{ record: any; parent?: any }> = (props) => {
const { record, children, parent = false } = props;
export const RecordProvider: React.FC<{ record: any; parent?: any; collectionName?: string }> = (props) => {
const { record, children, collectionName, parent = false } = props;
const { name: __collectionName } = useCollection();
const __parent = useContext(RecordContext);
const value = { ...record };
value['__parent'] = parent ? parent : __parent;
value['__collectionName'] = collectionName || __collectionName;
return <RecordContext.Provider value={value}>{children}</RecordContext.Provider>;
};

View File

@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
import { useActionContext } from '../..';
import { useDesignable } from '../../';
import { Icon } from '../../../icon';
import { useRecord } from '../../../record-provider';
import { RecordProvider, useRecord } from '../../../record-provider';
import { useLocalVariables, useVariables } from '../../../variables';
import { SortableItem } from '../../common';
import { useCompile, useComponent, useDesigner } from '../../hooks';
@ -52,7 +52,7 @@ export const Action: ComposedAction = observer(
const fieldSchema = useFieldSchema();
const compile = useCompile();
const form = useForm();
const values = useRecord();
const record = useRecord();
const designerProps = fieldSchema['x-designer-props'];
const openMode = fieldSchema?.['x-component-props']?.['openMode'];
const disabled = form.disabled || field.disabled || field.data?.disabled || props.disabled;
@ -61,7 +61,12 @@ export const Action: ComposedAction = observer(
const tarComponent = useComponent(component) || component;
const { modal } = App.useApp();
const variables = useVariables();
const localVariables = useLocalVariables({ currentForm: { values } as any });
const localVariables = useLocalVariables({ currentForm: { values: record } as any });
// fix https://nocobase.height.app/T-2259
const shouldResetRecord = ['create', 'customize:bulkUpdate', 'customize:bulkEdit', 'customize:create'].includes(
fieldSchema['x-action'],
);
let actionTitle = title || compile(fieldSchema.title);
actionTitle = lodash.isString(actionTitle) ? t(actionTitle) : actionTitle;
@ -75,13 +80,13 @@ export const Action: ComposedAction = observer(
operator: h.operator,
field,
condition: v.condition,
values,
values: record,
variables,
localVariables,
});
});
});
}, [JSON.stringify(linkageRules), values, designable, field]);
}, [JSON.stringify(linkageRules), record, designable, field]);
const handleButtonClick = useCallback(
(e: React.MouseEvent) => {
@ -142,7 +147,7 @@ export const Action: ComposedAction = observer(
);
};
return wrapSSR(
const result = (
<ActionContextProvider
button={renderButton()}
visible={visible}
@ -158,7 +163,17 @@ export const Action: ComposedAction = observer(
{!popover && renderButton()}
{!popover && props.children}
{element}
</ActionContextProvider>,
</ActionContextProvider>
);
return wrapSSR(
shouldResetRecord ? (
<RecordProvider parent={record} record={{}}>
{result}
</RecordProvider>
) : (
result
),
);
},
{ displayName: 'Action' },

View File

@ -15,7 +15,6 @@ import { DEBOUNCE_WAIT } from '../../../variables';
import { getPath } from '../../../variables/utils/getPath';
import { isVariable } from '../../../variables/utils/isVariable';
import { useDesignable } from '../../hooks';
import { removeNullCondition } from '../filter';
import { AssociationFieldContext } from './context';
export const useInsertSchema = (component) => {

View File

@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { FixedBlockDesignerItem, removeNullCondition, useDesignable } from '../..';
import { useCalendarBlockContext, useFormBlockContext } from '../../../block-provider';
import { useCollection, useCollectionManager } from '../../../collection-manager';
import { RecordProvider, useRecord } from '../../../record-provider';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
import { useSchemaTemplate } from '../../../schema-templates';
@ -17,10 +18,12 @@ export const CalendarDesigner = () => {
const { dn } = useDesignable();
const { t } = useTranslation();
const template = useSchemaTemplate();
const record = useRecord();
const fieldNames = fieldSchema?.['x-decorator-props']?.['fieldNames'] || {};
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
return (
<RecordProvider parent={record} record={{}}>
<GeneralSchemaDesigner template={template} title={title || name}>
<SchemaSettings.BlockTitleItem />
<SchemaSettings.SelectItem
@ -131,5 +134,6 @@ export const CalendarDesigner = () => {
}}
/>
</GeneralSchemaDesigner>
</RecordProvider>
);
};

View File

@ -735,7 +735,7 @@ FormItem.FilterFormDesigner = FilterFormDesigner;
function useIsAddNewForm() {
const record = useRecord();
const isAddNewForm = _.isEmpty(_.omit(record, '__parent'));
const isAddNewForm = _.isEmpty(_.omit(record, ['__parent', '__collectionName']));
return isAddNewForm;
}

View File

@ -39,6 +39,7 @@ const useLazyLoadAssociationFieldOfForm = () => {
const cloneRecord = { ...record };
delete cloneRecord['__parent'];
delete cloneRecord['__collectionName'];
if (_.isEmpty(cloneRecord) || !variables || record[schemaName] != null) {
return;

View File

@ -2,7 +2,8 @@ import { FormLayout } from '@formily/antd-v5';
import { createForm } from '@formily/core';
import { FormContext, useField } from '@formily/react';
import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { BlockProvider, useBlockRequestContext } from '../../../block-provider';
import { BlockProvider, useBlockRequestContext, useParsedFilter } from '../../../block-provider';
import { useRecord } from '../../../record-provider';
import useStyles from './GridCard.Decorator.style';
export const GridCardBlockContext = createContext<any>({});
@ -41,8 +42,22 @@ const InternalGridCardBlockProvider = (props) => {
};
export const GridCardBlockProvider = (props) => {
const { params } = props;
const record = useRecord();
const { filter: parsedFilter } = useParsedFilter({
filterOption: params?.filter,
currentRecord: { __parent: record, __collectionName: props.collection },
});
const paramsWithFilter = useMemo(() => {
return {
...params,
filter: parsedFilter,
};
}, [parsedFilter, params]);
return (
<BlockProvider data-testid="grid-card-block" {...props}>
<BlockProvider data-testid="grid-card-block" {...props} params={paramsWithFilter}>
<InternalGridCardBlockProvider {...props} />
</BlockProvider>
);

View File

@ -6,6 +6,7 @@ import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useFormBlockContext } from '../../../block-provider';
import { useCollection, useSortFields } from '../../../collection-manager';
import { RecordProvider, useRecord } from '../../../record-provider';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
import { useSchemaTemplate } from '../../../schema-templates';
import { SchemaComponentOptions } from '../../core';
@ -27,6 +28,7 @@ export const GridCardDesigner = () => {
const field = useField();
const { dn } = useDesignable();
const sortFields = useSortFields(name);
const record = useRecord();
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
const columnCount = field.decoratorProps.columnCount || defaultColumnCount;
@ -70,6 +72,8 @@ export const GridCardDesigner = () => {
};
});
return (
// fix https://nocobase.height.app/T-2259
<RecordProvider parent={record} record={{}}>
<GeneralSchemaDesigner template={template} title={title || name}>
<SchemaComponentOptions components={{ Slider }}>
<SchemaSettings.ModalItem
@ -224,5 +228,6 @@ export const GridCardDesigner = () => {
/>
</SchemaComponentOptions>
</GeneralSchemaDesigner>
</RecordProvider>
);
};

View File

@ -3,7 +3,8 @@ import { FormLayout } from '@formily/antd-v5';
import { createForm } from '@formily/core';
import { FormContext, useField } from '@formily/react';
import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { BlockProvider, useBlockRequestContext } from '../../../block-provider';
import { BlockProvider, useBlockRequestContext, useParsedFilter } from '../../../block-provider';
import { useRecord } from '../../../record-provider';
export const ListBlockContext = createContext<any>({});
@ -51,8 +52,22 @@ const InternalListBlockProvider = (props) => {
};
export const ListBlockProvider = (props) => {
const { params } = props;
const record = useRecord();
const { filter: parsedFilter } = useParsedFilter({
filterOption: params?.filter,
currentRecord: { __parent: record, __collectionName: props.collection },
});
const paramsWithFilter = useMemo(() => {
return {
...params,
filter: parsedFilter,
};
}, [parsedFilter, params]);
return (
<BlockProvider data-testid="list-block" {...props}>
<BlockProvider data-testid="list-block" {...props} params={paramsWithFilter}>
<InternalListBlockProvider {...props} />
</BlockProvider>
);

View File

@ -5,6 +5,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { useFormBlockContext } from '../../../block-provider';
import { useCollection, useSortFields } from '../../../collection-manager';
import { RecordProvider, useRecord } from '../../../record-provider';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
import { useSchemaTemplate } from '../../../schema-templates';
import { useDesignable } from '../../hooks';
@ -19,6 +20,7 @@ export const ListDesigner = () => {
const field = useField();
const { dn } = useDesignable();
const sortFields = useSortFields(name);
const record = useRecord();
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
const sort = defaultSort?.map((item: string) => {
@ -33,6 +35,8 @@ export const ListDesigner = () => {
};
});
return (
// fix https://nocobase.height.app/T-2259
<RecordProvider parent={record} record={{}}>
<GeneralSchemaDesigner template={template} title={title || name}>
<SchemaSettings.BlockTitleItem />
<SchemaSettings.DataScope
@ -170,5 +174,6 @@ export const ListDesigner = () => {
}}
/>
</GeneralSchemaDesigner>
</RecordProvider>
);
};

View File

@ -7,6 +7,7 @@ import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
import { useCollection, useCollectionManager } from '../../../collection-manager';
import { useSortFields } from '../../../collection-manager/action-hooks';
import { FilterBlockType } from '../../../filter-provider/utils';
import { RecordProvider, useRecord } from '../../../record-provider';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
import { useSchemaTemplate } from '../../../schema-templates';
import { useDesignable } from '../../hooks';
@ -23,6 +24,8 @@ export const TableBlockDesigner = () => {
const { service } = useTableBlockContext();
const { t } = useTranslation();
const { dn } = useDesignable();
const record = useRecord();
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
const supportTemplate = !fieldSchema?.['x-decorator-props']?.disableTemplate;
@ -64,6 +67,8 @@ export const TableBlockDesigner = () => {
[dn, field.decoratorProps, fieldSchema, service],
);
return (
// fix https://nocobase.height.app/T-2259
<RecordProvider parent={record} record={{}}>
<GeneralSchemaDesigner template={template} title={title || name}>
<SchemaSettings.BlockTitleItem />
{collection?.tree && collectionField?.collectionName === collectionField?.target && (
@ -241,5 +246,6 @@ export const TableBlockDesigner = () => {
}}
/>
</GeneralSchemaDesigner>
</RecordProvider>
);
};

View File

@ -0,0 +1,29 @@
import { Schema } from '@formily/json-schema';
import { useTranslation } from 'react-i18next';
import { CollectionFieldOptions } from '../../../collection-manager';
import { useBaseVariable } from './useBaseVariable';
interface Props {
collectionField: CollectionFieldOptions;
schema: any;
collectionName: string;
noDisabled?: boolean;
/** 消费变量值的字段 */
targetFieldSchema?: Schema;
}
export const useParentRecordVariable = (props: Props) => {
const { t } = useTranslation();
const currentRecordVariable = useBaseVariable({
collectionField: props.collectionField,
uiSchema: props.schema,
name: '$nParentRecord',
title: t('Current parent record'),
collectionName: props.collectionName,
noDisabled: props.noDisabled,
targetFieldSchema: props.targetFieldSchema,
});
return currentRecordVariable;
};

View File

@ -1,13 +1,14 @@
import { Form } from '@formily/core';
import { ISchema, Schema } from '@formily/react';
import _ from 'lodash';
import { useMemo } from 'react';
import { useFormBlockType } from '../../../block-provider/FormBlockProvider';
import { CollectionFieldOptions, useCollection } from '../../../collection-manager';
import { useFlag } from '../../../flag-provider';
import { useBlockCollection } from './useBlockCollection';
import { useDateVariable } from './useDateVariable';
import { useFormVariable } from './useFormVariable';
import { useIterationVariable } from './useIterationVariable';
import { useParentRecordVariable } from './useParentRecordVariable';
import { useRecordVariable } from './useRecordVariable';
import { useUserVariable } from './useUserVariable';
@ -44,11 +45,12 @@ export const useVariableOptions = ({
operator,
noDisabled,
targetFieldSchema,
record,
}: Props) => {
const { name: blockCollectionName } = useBlockCollection();
const { name: blockCollectionName = record?.__collectionName } = useBlockCollection();
const { isInSubForm, isInSubTable } = useFlag() || {};
const { type: formBlockType } = useFormBlockType();
const { name } = useCollection();
const blockParentCollectionName = record?.__parent?.__collectionName;
const userVariable = useUserVariable({
maxDepth: 3,
uiSchema: uiSchema,
@ -78,6 +80,13 @@ export const useVariableOptions = ({
noDisabled,
targetFieldSchema,
});
const currentParentRecordVariable = useParentRecordVariable({
schema: uiSchema,
collectionName: blockParentCollectionName,
collectionField,
noDisabled,
targetFieldSchema,
});
return useMemo(() => {
return [
@ -85,7 +94,10 @@ export const useVariableOptions = ({
dateVariable,
form && !form.readPretty && formVariable,
(isInSubForm || isInSubTable) && iterationVariable,
formBlockType === 'update' && currentRecordVariable,
blockCollectionName && !_.isEmpty(_.omit(record, ['__parent', '__collectionName'])) && currentRecordVariable,
blockParentCollectionName &&
!_.isEmpty(_.omit(record?.__parent, ['__parent', '__collectionName'])) &&
currentParentRecordVariable,
].filter(Boolean);
}, [
userVariable,
@ -95,6 +107,10 @@ export const useVariableOptions = ({
isInSubForm,
isInSubTable,
iterationVariable,
blockCollectionName,
record,
currentRecordVariable,
blockParentCollectionName,
currentParentRecordVariable,
]);
};

View File

@ -9,14 +9,15 @@ interface Props {
* ['$user', '$date']
*
*/
exclude: string[];
exclude?: string[];
currentRecord?: any;
}
// TODO: 建议变量名统一命名为 `$n` 开头,以防止与 formily 内置变量冲突
const defaultExclude = ['$user', '$date', '$nDate'];
const useParseDataScopeFilter = ({ exclude }: Props = { exclude: defaultExclude }) => {
const localVariables = useLocalVariables();
const useParseDataScopeFilter = ({ exclude = defaultExclude, currentRecord }: Props = {}) => {
const localVariables = useLocalVariables({ currentRecord });
const variables = useVariables();
const parseFilter = useCallback(

View File

@ -1,5 +1,4 @@
import { Form } from '@formily/core';
import _ from 'lodash';
import React, { useContext, useMemo } from 'react';
import { useFormBlockContext } from '../../block-provider';
import { useCollection } from '../../collection-manager';
@ -42,12 +41,6 @@ const useLocalVariables = (props?: Props) => {
name = props.collectionName;
}
let blockRecord = currentRecord;
// 获取到最顶层的 record即当前区块所在的 record
while (!_.isEmpty(blockRecord.__parent)) {
blockRecord = blockRecord.__parent;
}
return useMemo(() => {
return (
[
@ -57,7 +50,7 @@ const useLocalVariables = (props?: Props) => {
*/
{
name: 'currentRecord',
ctx: blockRecord,
ctx: currentRecord,
collectionName: name,
},
/**
@ -66,7 +59,7 @@ const useLocalVariables = (props?: Props) => {
*/
{
name,
ctx: form?.values || blockRecord,
ctx: form?.values || currentRecord,
collectionName: name,
},
/**
@ -80,8 +73,13 @@ const useLocalVariables = (props?: Props) => {
},
{
name: '$nRecord',
ctx: blockRecord,
collectionName: name,
ctx: currentRecord,
collectionName: currentRecord?.__collectionName,
},
{
name: '$nParentRecord',
ctx: currentRecord?.__parent,
collectionName: currentRecord?.__parent?.__collectionName,
},
{
name: '$nForm',
@ -91,7 +89,7 @@ const useLocalVariables = (props?: Props) => {
iterationCtx && { name: '$iteration', ctx: iterationCtx, collectionName: currentCollectionName },
] as VariableOption[]
).filter(Boolean);
}, [blockRecord, name, form?.values, iterationCtx, currentCollectionName]); // 尽量保持返回的值不变,这样可以减少接口的请求次数,因为关系字段会缓存到变量的 ctx 中
}, [currentRecord, name, form?.values, iterationCtx, currentCollectionName]); // 尽量保持返回的值不变,这样可以减少接口的请求次数,因为关系字段会缓存到变量的 ctx 中
};
export default useLocalVariables;

View File

@ -148,7 +148,6 @@ export function useTriggerWorkflowsActionProps() {
return {
async onClick() {
const fieldNames = fields.map((field) => field.name);
const {
assignedValues: originalAssignedValues = {},
onSuccess,