perf: avoid page lag or stuttering (#2964)
* perf: avoid page lag or stuttering * perf: improve performance of data scope * chore: remove console.log * perf: avoid crashing * fix: make tests passing * fix: fix title of dialog
This commit is contained in:
		
							parent
							
								
									a42ee95e03
								
							
						
					
					
						commit
						bce02ad1d7
					
				@ -94,7 +94,7 @@ test.describe('menu page', () => {
 | 
			
		||||
 | 
			
		||||
    await page.getByLabel('Insert before').hover();
 | 
			
		||||
 | 
			
		||||
    await page.getByRole('button', { name: 'Add page' }).click();
 | 
			
		||||
    await page.getByRole('button', { name: 'Page', exact: true }).click();
 | 
			
		||||
    await page.getByRole('textbox').fill(pageTitle4);
 | 
			
		||||
    await page.getByRole('button', { name: 'OK' }).click();
 | 
			
		||||
 | 
			
		||||
@ -124,7 +124,7 @@ test.describe('menu page', () => {
 | 
			
		||||
 | 
			
		||||
    await page.getByLabel('Insert after').hover();
 | 
			
		||||
 | 
			
		||||
    await page.getByRole('button', { name: 'Add page' }).click();
 | 
			
		||||
    await page.getByRole('button', { name: 'Page', exact: true }).click();
 | 
			
		||||
    await page.getByRole('textbox').fill(pageTitle6);
 | 
			
		||||
    await page.getByRole('button', { name: 'OK' }).click();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { enableToConfig, expect, test } from '@nocobase/test/client';
 | 
			
		||||
import { expect, test } from '@nocobase/test/client';
 | 
			
		||||
 | 
			
		||||
test.describe('page header', () => {
 | 
			
		||||
  test('disabled & enabled page header', async ({ page, mockPage }) => {
 | 
			
		||||
@ -99,7 +99,7 @@ test.describe('page tabs', () => {
 | 
			
		||||
    //修改tab名称
 | 
			
		||||
    await page.getByText('Unnamed').click();
 | 
			
		||||
    await page.getByRole('button', { name: 'designer-schema-settings-Page-tab' }).click();
 | 
			
		||||
    await page.getByLabel('Edit tab').click();
 | 
			
		||||
    await page.getByLabel('Edit', { exact: true }).click();
 | 
			
		||||
    await page.getByRole('textbox').fill('page tab');
 | 
			
		||||
    await page.getByRole('button', { name: 'OK' }).click();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -64,7 +64,6 @@ const useResource = (props: UseResourceProps) => {
 | 
			
		||||
  const { fieldSchema } = useActionContext();
 | 
			
		||||
  const isCreateAction = fieldSchema?.['x-action'] === 'create';
 | 
			
		||||
  const association = useAssociation(props);
 | 
			
		||||
  console.log(association);
 | 
			
		||||
  const sourceId = useSourceId?.();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const withoutTableFieldResource = useContext(WithoutTableFieldResource);
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
import { useField, useForm } from '@formily/react';
 | 
			
		||||
import { message } from 'antd';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import cloneDeep from 'lodash/cloneDeep';
 | 
			
		||||
import omit from 'lodash/omit';
 | 
			
		||||
import { useEffect, useMemo } from 'react';
 | 
			
		||||
import { useCallback, useEffect, useMemo } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useCollection, useCollectionManager } from '.';
 | 
			
		||||
import { useRequest } from '../api-client';
 | 
			
		||||
@ -183,7 +184,65 @@ export const useCollectionFilterOptions = (collection: any) => {
 | 
			
		||||
    };
 | 
			
		||||
    const options = getOptions(fields, 1);
 | 
			
		||||
    return options;
 | 
			
		||||
  }, [collection]);
 | 
			
		||||
  }, [_.isString(collection) ? collection : collection?.name]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useCollectionFilterOptionsV2 = (collection: any) => {
 | 
			
		||||
  const { getCollectionFields, getInterface } = useCollectionManager();
 | 
			
		||||
 | 
			
		||||
  const getFields = useCallback(() => {
 | 
			
		||||
    const fields = getCollectionFields(collection);
 | 
			
		||||
    const field2option = (field, depth) => {
 | 
			
		||||
      if (!field.interface) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      const fieldInterface = getInterface(field.interface);
 | 
			
		||||
      if (!fieldInterface?.filterable) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      const { nested, children, operators } = fieldInterface.filterable;
 | 
			
		||||
      const option = {
 | 
			
		||||
        name: field.name,
 | 
			
		||||
        title: field?.uiSchema?.title || field.name,
 | 
			
		||||
        schema: field?.uiSchema,
 | 
			
		||||
        operators:
 | 
			
		||||
          operators?.filter?.((operator) => {
 | 
			
		||||
            return !operator?.visible || operator.visible(field);
 | 
			
		||||
          }) || [],
 | 
			
		||||
        interface: field.interface,
 | 
			
		||||
      };
 | 
			
		||||
      if (field.target && depth > 2) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (depth > 2) {
 | 
			
		||||
        return option;
 | 
			
		||||
      }
 | 
			
		||||
      if (children?.length) {
 | 
			
		||||
        option['children'] = children;
 | 
			
		||||
      }
 | 
			
		||||
      if (nested) {
 | 
			
		||||
        const targetFields = getCollectionFields(field.target);
 | 
			
		||||
        const options = getOptions(targetFields, depth + 1).filter(Boolean);
 | 
			
		||||
        option['children'] = option['children'] || [];
 | 
			
		||||
        option['children'].push(...options);
 | 
			
		||||
      }
 | 
			
		||||
      return option;
 | 
			
		||||
    };
 | 
			
		||||
    const getOptions = (fields, depth) => {
 | 
			
		||||
      const options = [];
 | 
			
		||||
      fields.forEach((field) => {
 | 
			
		||||
        const option = field2option(field, depth);
 | 
			
		||||
        if (option) {
 | 
			
		||||
          options.push(option);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      return options;
 | 
			
		||||
    };
 | 
			
		||||
    const options = getOptions(fields, 1);
 | 
			
		||||
    return options;
 | 
			
		||||
  }, [_.isString(collection) ? collection : collection?.name]);
 | 
			
		||||
 | 
			
		||||
  return { getFields };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useLinkageCollectionFilterOptions = (collectionName: string) => {
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,6 @@
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { FormLayout } from '@formily/antd-v5';
 | 
			
		||||
import {
 | 
			
		||||
  createForm,
 | 
			
		||||
  Field,
 | 
			
		||||
  Form as FormilyForm,
 | 
			
		||||
  onFieldChange,
 | 
			
		||||
  onFieldInit,
 | 
			
		||||
  onFieldReact,
 | 
			
		||||
  onFormInputChange,
 | 
			
		||||
} from '@formily/core';
 | 
			
		||||
import { createForm, Field, Form as FormilyForm, onFieldChange, onFieldInit, onFormInputChange } from '@formily/core';
 | 
			
		||||
import { FieldContext, FormContext, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { autorun } from '@formily/reactive';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
@ -132,7 +124,7 @@ const WithForm = (props: WithFormProps) => {
 | 
			
		||||
              };
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 代替
 | 
			
		||||
            // 之前使用的 `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 和 `autorun` 代替
 | 
			
		||||
            onFieldInit(`*(${fields})`, (field: any, form) => {
 | 
			
		||||
              disposes.push(
 | 
			
		||||
                autorun(async () => {
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ export const linkageMergeAction = async ({
 | 
			
		||||
  const requiredResult = field?.linkageProperty?.required || [field?.initProperty?.required || false];
 | 
			
		||||
  const displayResult = field?.linkageProperty?.display || [field?.initProperty?.display];
 | 
			
		||||
  const patternResult = field?.linkageProperty?.pattern || [field?.initProperty?.pattern];
 | 
			
		||||
  const valueResult = field?.linkageProperty?.value || [field.value || field?.initProperty?.value];
 | 
			
		||||
  const valueResult = field?.linkageProperty?.value || [field?.initProperty?.value];
 | 
			
		||||
  const { evaluate } = evaluators.get('formula.js');
 | 
			
		||||
 | 
			
		||||
  switch (operator) {
 | 
			
		||||
@ -130,7 +130,10 @@ export const linkageMergeAction = async ({
 | 
			
		||||
          ...field.linkageProperty,
 | 
			
		||||
          value: valueResult,
 | 
			
		||||
        };
 | 
			
		||||
        field.value = last(valueResult) === undefined ? field.value : last(valueResult);
 | 
			
		||||
 | 
			
		||||
        if (last(valueResult) !== undefined) {
 | 
			
		||||
          field.value = last(valueResult);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
 | 
			
		||||
@ -55,7 +55,6 @@ import {
 | 
			
		||||
  useActionContext,
 | 
			
		||||
  useBlockRequestContext,
 | 
			
		||||
  useCollection,
 | 
			
		||||
  useCollectionFilterOptions,
 | 
			
		||||
  useCollectionManager,
 | 
			
		||||
  useCompile,
 | 
			
		||||
  useDesignable,
 | 
			
		||||
@ -72,6 +71,7 @@ import {
 | 
			
		||||
  updateFilterTargets,
 | 
			
		||||
  useFormActiveFields,
 | 
			
		||||
} from '../block-provider/hooks';
 | 
			
		||||
import { useCollectionFilterOptionsV2 } from '../collection-manager/action-hooks';
 | 
			
		||||
import {
 | 
			
		||||
  FilterBlockType,
 | 
			
		||||
  getSupportFieldsByAssociation,
 | 
			
		||||
@ -133,7 +133,7 @@ interface ModalItemProps {
 | 
			
		||||
  title: string;
 | 
			
		||||
  onSubmit: (values: any) => void;
 | 
			
		||||
  initialValues?: any;
 | 
			
		||||
  schema?: ISchema;
 | 
			
		||||
  schema?: ISchema | (() => ISchema);
 | 
			
		||||
  modalTip?: string;
 | 
			
		||||
  components?: any;
 | 
			
		||||
  hidden?: boolean;
 | 
			
		||||
@ -937,7 +937,6 @@ SchemaSettings.ModalItem = function ModalItem(props: ModalItemProps) {
 | 
			
		||||
    components,
 | 
			
		||||
    scope,
 | 
			
		||||
    effects,
 | 
			
		||||
    schema,
 | 
			
		||||
    onSubmit,
 | 
			
		||||
    asyncGetInitialValues,
 | 
			
		||||
    initialValues,
 | 
			
		||||
@ -957,10 +956,11 @@ SchemaSettings.ModalItem = function ModalItem(props: ModalItemProps) {
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettings.Item
 | 
			
		||||
      title={schema.title || title}
 | 
			
		||||
      title={title}
 | 
			
		||||
      {...others}
 | 
			
		||||
      onClick={async () => {
 | 
			
		||||
        const values = asyncGetInitialValues ? await asyncGetInitialValues() : initialValues;
 | 
			
		||||
        const schema = _.isFunction(props.schema) ? props.schema() : props.schema;
 | 
			
		||||
        FormDialog(
 | 
			
		||||
          { title: schema.title || title, width },
 | 
			
		||||
          () => {
 | 
			
		||||
@ -1585,6 +1585,7 @@ SchemaSettings.DefaultValue = function DefaultValueConfigure(props: { fieldSchem
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const actionCtx = useActionContext();
 | 
			
		||||
  let targetField;
 | 
			
		||||
 | 
			
		||||
  const { getField } = useCollection();
 | 
			
		||||
  const { getCollectionJoinField, getCollectionFields, getAllCollectionsInheritChain } = useCollectionManager();
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
@ -1592,17 +1593,21 @@ SchemaSettings.DefaultValue = function DefaultValueConfigure(props: { fieldSchem
 | 
			
		||||
  const collection = useCollection();
 | 
			
		||||
  const record = useRecord();
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const currentFormFields = useCollectionFilterOptions(collection);
 | 
			
		||||
  const { getFields } = useCollectionFilterOptionsV2(collection);
 | 
			
		||||
  const { isInSubForm, isInSubTable } = useFlag() || {};
 | 
			
		||||
 | 
			
		||||
  const { name } = collection;
 | 
			
		||||
  const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
 | 
			
		||||
  const collectionField = useMemo(
 | 
			
		||||
    () => getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']),
 | 
			
		||||
    [fieldSchema, getCollectionJoinField, getField],
 | 
			
		||||
  );
 | 
			
		||||
  const fieldSchemaWithoutRequired = _.omit(fieldSchema, 'required');
 | 
			
		||||
  if (collectionField?.target) {
 | 
			
		||||
    targetField = getCollectionJoinField(
 | 
			
		||||
      `${collectionField.target}.${fieldSchema['x-component-props']?.fieldNames?.label || 'id'}`,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const parentFieldSchema = collectionField?.interface === 'm2o' && findParentFieldSchema(fieldSchema);
 | 
			
		||||
  const parentCollectionField = parentFieldSchema && getCollectionJoinField(parentFieldSchema?.['x-collection-field']);
 | 
			
		||||
  const tableCtx = useTableBlockContext();
 | 
			
		||||
@ -1619,18 +1624,16 @@ SchemaSettings.DefaultValue = function DefaultValueConfigure(props: { fieldSchem
 | 
			
		||||
      // fix https://nocobase.height.app/T-1355
 | 
			
		||||
      // 工作流人工节点的 `自定义表单` 区块,与其它表单区块不同,根据它的数据表名称,获取到的字段列表为空,所以需要在这里特殊处理一下
 | 
			
		||||
      if (!fields?.length && currentForm) {
 | 
			
		||||
        currentForm.children = formatVariableScop(currentFormFields);
 | 
			
		||||
        currentForm.children = formatVariableScop(getFields());
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return scope;
 | 
			
		||||
    },
 | 
			
		||||
    [currentFormFields, name],
 | 
			
		||||
    [getFields, name],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettings.ModalItem
 | 
			
		||||
      title={t('Set default value')}
 | 
			
		||||
      components={{
 | 
			
		||||
  const DefaultValueComponent: any = useMemo(() => {
 | 
			
		||||
    return {
 | 
			
		||||
      ArrayCollapse,
 | 
			
		||||
      FormLayout,
 | 
			
		||||
      VariableInput: (props) => {
 | 
			
		||||
@ -1640,10 +1643,11 @@ SchemaSettings.DefaultValue = function DefaultValueConfigure(props: { fieldSchem
 | 
			
		||||
          </FlagProvider>
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
      }}
 | 
			
		||||
      width={800}
 | 
			
		||||
      schema={
 | 
			
		||||
        {
 | 
			
		||||
    };
 | 
			
		||||
  }, [isInSubForm, isInSubTable]);
 | 
			
		||||
 | 
			
		||||
  const schema = useMemo(() => {
 | 
			
		||||
    return {
 | 
			
		||||
      type: 'object',
 | 
			
		||||
      title: t('Set default value'),
 | 
			
		||||
      properties: {
 | 
			
		||||
@ -1720,9 +1724,24 @@ SchemaSettings.DefaultValue = function DefaultValueConfigure(props: { fieldSchem
 | 
			
		||||
          default: getFieldDefaultValue(fieldSchema, collectionField),
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
        } as ISchema
 | 
			
		||||
      }
 | 
			
		||||
      onSubmit={(v) => {
 | 
			
		||||
    } as ISchema;
 | 
			
		||||
  }, [
 | 
			
		||||
    collectionField,
 | 
			
		||||
    fieldSchema,
 | 
			
		||||
    fieldSchemaWithoutRequired,
 | 
			
		||||
    form,
 | 
			
		||||
    getAllCollectionsInheritChain,
 | 
			
		||||
    isAllowContextVariable,
 | 
			
		||||
    localVariables,
 | 
			
		||||
    record,
 | 
			
		||||
    returnScope,
 | 
			
		||||
    t,
 | 
			
		||||
    tableCtx.collection,
 | 
			
		||||
    targetField,
 | 
			
		||||
    variables,
 | 
			
		||||
  ]);
 | 
			
		||||
  const handleSubmit: (values: any) => void = useCallback(
 | 
			
		||||
    (v) => {
 | 
			
		||||
      const schema: ISchema = {
 | 
			
		||||
        ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
      };
 | 
			
		||||
@ -1735,7 +1754,17 @@ SchemaSettings.DefaultValue = function DefaultValueConfigure(props: { fieldSchem
 | 
			
		||||
        schema,
 | 
			
		||||
        currentSchema,
 | 
			
		||||
      });
 | 
			
		||||
      }}
 | 
			
		||||
    },
 | 
			
		||||
    [currentSchema, dn, field, fieldSchema],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettings.ModalItem
 | 
			
		||||
      title={t('Set default value')}
 | 
			
		||||
      components={DefaultValueComponent}
 | 
			
		||||
      width={800}
 | 
			
		||||
      schema={schema}
 | 
			
		||||
      onSubmit={handleSubmit}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@ -1858,7 +1887,7 @@ SchemaSettings.SortingRule = function SortRuleConfigure(props) {
 | 
			
		||||
 | 
			
		||||
SchemaSettings.DataScope = function DataScopeConfigure(props: DataScopeProps) {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const options = useCollectionFilterOptions(props.collectionName);
 | 
			
		||||
  const { getFields } = useCollectionFilterOptionsV2(props.collectionName);
 | 
			
		||||
  const record = useRecord();
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
@ -1866,7 +1895,8 @@ SchemaSettings.DataScope = function DataScopeConfigure(props: DataScopeProps) {
 | 
			
		||||
  const { getAllCollectionsInheritChain } = useCollectionManager();
 | 
			
		||||
  const { isInSubForm, isInSubTable } = useFlag() || {};
 | 
			
		||||
 | 
			
		||||
  const dynamicComponent = (props: DynamicComponentProps) => {
 | 
			
		||||
  const dynamicComponent = useCallback(
 | 
			
		||||
    (props: DynamicComponentProps) => {
 | 
			
		||||
      return (
 | 
			
		||||
        <DatePickerProvider value={{ utc: false }}>
 | 
			
		||||
          <VariableInput
 | 
			
		||||
@ -1882,19 +1912,17 @@ SchemaSettings.DataScope = function DataScopeConfigure(props: DataScopeProps) {
 | 
			
		||||
          />
 | 
			
		||||
        </DatePickerProvider>
 | 
			
		||||
      );
 | 
			
		||||
  };
 | 
			
		||||
    },
 | 
			
		||||
    [form, getAllCollectionsInheritChain, localVariables, record, variables],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettings.ModalItem
 | 
			
		||||
      title={t('Set the data scope')}
 | 
			
		||||
      initialValues={{ filter: props.defaultFilter }}
 | 
			
		||||
      schema={
 | 
			
		||||
        {
 | 
			
		||||
  const getSchema = () => {
 | 
			
		||||
    return {
 | 
			
		||||
      type: 'object',
 | 
			
		||||
      title: t('Set the data scope'),
 | 
			
		||||
      properties: {
 | 
			
		||||
        filter: {
 | 
			
		||||
              enum: props.collectionFilterOption || options,
 | 
			
		||||
          enum: props.collectionFilterOption || getFields(),
 | 
			
		||||
          'x-decorator': (props) => (
 | 
			
		||||
            <BaseVariableProvider {...props}>
 | 
			
		||||
              <FlagProvider isInSubForm={isInSubForm} isInSubTable={isInSubTable}>
 | 
			
		||||
@ -1912,8 +1940,14 @@ SchemaSettings.DataScope = function DataScopeConfigure(props: DataScopeProps) {
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
        } as ISchema
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettings.ModalItem
 | 
			
		||||
      title={t('Set the data scope')}
 | 
			
		||||
      initialValues={{ filter: props.defaultFilter }}
 | 
			
		||||
      schema={getSchema as () => ISchema}
 | 
			
		||||
      onSubmit={props.onSubmit}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user