fix(linkageRules): avoid infinite loop (#3095)
* Revert "refactor: avoid errors (#3091)"
This reverts commit 85be616f35
.
* fix(linkageRules): avoid infinite loop
* chore: remove console.log
* chore: remove useless code
This commit is contained in:
parent
6819366049
commit
723cc45d0f
@ -1,16 +1,17 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { FormLayout } from '@formily/antd-v5';
|
import { FormLayout } from '@formily/antd-v5';
|
||||||
import { createForm, Field, Form as FormilyForm, onFieldChange, onFieldInit, onFormInputChange } from '@formily/core';
|
import { createForm, Field, Form as FormilyForm, onFieldInit, onFormInputChange } from '@formily/core';
|
||||||
import { FieldContext, FormContext, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
import { FieldContext, FormContext, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||||
import { autorun, raw } from '@formily/reactive';
|
import { autorun, toJS } from '@formily/reactive';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { ConfigProvider, Spin } from 'antd';
|
import { ConfigProvider, Spin } from 'antd';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { useActionContext } from '..';
|
import { useActionContext } from '..';
|
||||||
import { useAttach, useComponent } from '../..';
|
import { useAttach, useComponent } from '../..';
|
||||||
|
import { ActionType } from '../../../schema-settings/LinkageRules/type';
|
||||||
import { useLocalVariables, useVariables } from '../../../variables';
|
import { useLocalVariables, useVariables } from '../../../variables';
|
||||||
import { useProps } from '../../hooks/useProps';
|
import { useProps } from '../../hooks/useProps';
|
||||||
import { linkageMergeAction } from './utils';
|
import { collectFieldStateOfLinkageRules, getTempFieldState } from './utils';
|
||||||
|
|
||||||
export interface FormProps {
|
export interface FormProps {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
@ -105,38 +106,64 @@ const WithForm = (props: WithFormProps) => {
|
|||||||
const disposes = [];
|
const disposes = [];
|
||||||
|
|
||||||
form.addEffects(id, () => {
|
form.addEffects(id, () => {
|
||||||
linkageRules.forEach((v, index) => {
|
linkageRules.forEach((v) => {
|
||||||
v.actions?.forEach((h) => {
|
v.actions?.forEach((h) => {
|
||||||
if (h.targetFields?.length) {
|
if (h.targetFields?.length) {
|
||||||
const fields = h.targetFields.join(',');
|
const fields = h.targetFields.join(',');
|
||||||
onFieldInit(`*(${fields})`, (field: any, form) => {
|
onFieldInit(`*(${fields})`, (field: any, form) => {
|
||||||
field['initProperty'] = field?.['initProperty'] ?? {
|
field['initProperty'] = field?.['initProperty'] ?? {
|
||||||
display: field.display,
|
display: getTempFieldState(true, field.display),
|
||||||
required: field.required,
|
required: getTempFieldState(true, field.required),
|
||||||
pattern: field.pattern,
|
pattern: getTempFieldState(true, field.pattern),
|
||||||
value: field.value || field.initialValue,
|
value: getTempFieldState(true, field.value || field.initialValue),
|
||||||
};
|
|
||||||
});
|
|
||||||
onFieldChange(`*(${fields})`, ['value', 'required', 'pattern', 'display'], (field: any) => {
|
|
||||||
field.linkageProperty = {
|
|
||||||
display: field.linkageProperty?.display,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 之前使用的 `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 和 `autorun` 代替
|
// 之前使用的 `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 和 `autorun` 代替
|
||||||
onFieldInit(`*(${fields})`, (field: any, form) => {
|
onFieldInit(`*(${fields})`, (field: any, form) => {
|
||||||
field.linkageProperty = {};
|
|
||||||
disposes.push(
|
disposes.push(
|
||||||
autorun(async () => {
|
autorun(() => {
|
||||||
await linkageMergeAction({
|
// 当条件改变触发 autorun 时,会同步收集字段状态,并保存到 field.linkageProperty 中
|
||||||
|
collectFieldStateOfLinkageRules({
|
||||||
operator: h.operator,
|
operator: h.operator,
|
||||||
value: h.value,
|
value: h.value,
|
||||||
field,
|
field,
|
||||||
condition: v.condition,
|
condition: v.condition,
|
||||||
values: raw(form?.values),
|
values: toJS(form?.values),
|
||||||
variables,
|
variables,
|
||||||
localVariables,
|
localVariables,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 当条件改变时,有可能会触发多个 autorun,所以这里需要延迟一下,确保所有的 autorun 都执行完毕后,
|
||||||
|
// 再从 field.linkageProperty 中取值,因为此时 field.linkageProperty 中的值才是全的。
|
||||||
|
setTimeout(async () => {
|
||||||
|
const fieldName = getFieldNameByOperator(h.operator);
|
||||||
|
|
||||||
|
// 防止重复赋值
|
||||||
|
if (!field.linkageProperty[fieldName]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stateList = field.linkageProperty[fieldName];
|
||||||
|
|
||||||
|
stateList = await Promise.all(stateList);
|
||||||
|
stateList = stateList.filter((v) => v.condition);
|
||||||
|
|
||||||
|
const lastState = stateList[stateList.length - 1];
|
||||||
|
|
||||||
|
if (fieldName === 'value') {
|
||||||
|
// value 比较特殊,它只有在匹配条件时才需要赋值,当条件不匹配时,维持现在的值;
|
||||||
|
// stateList 中肯定会有一个初始值,所以当 stateList.length > 1 时,就说明有匹配条件的情况;
|
||||||
|
if (stateList.length > 1) {
|
||||||
|
field.value = lastState.value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
field[fieldName] = lastState?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在这里清空 field.linkageProperty,就可以保证:当条件再次改变时,如果该字段没有和任何条件匹配,则需要把对应的值恢复到初始值;
|
||||||
|
field.linkageProperty[fieldName] = null;
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -210,3 +237,23 @@ export const Form: React.FC<FormProps> & {
|
|||||||
},
|
},
|
||||||
{ displayName: 'Form' },
|
{ displayName: 'Form' },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function getFieldNameByOperator(operator: ActionType) {
|
||||||
|
switch (operator) {
|
||||||
|
case ActionType.Required:
|
||||||
|
case ActionType.InRequired:
|
||||||
|
return 'required';
|
||||||
|
case ActionType.Visible:
|
||||||
|
case ActionType.None:
|
||||||
|
case ActionType.Hidden:
|
||||||
|
return 'display';
|
||||||
|
case ActionType.Editable:
|
||||||
|
case ActionType.ReadOnly:
|
||||||
|
case ActionType.ReadPretty:
|
||||||
|
return 'pattern';
|
||||||
|
case ActionType.Value:
|
||||||
|
return 'value';
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { evaluators } from '@nocobase/evaluators/client';
|
import { evaluators } from '@nocobase/evaluators/client';
|
||||||
import { uid } from '@nocobase/utils/client';
|
import { uid } from '@nocobase/utils/client';
|
||||||
import _, { last } from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ActionType } from '../../../schema-settings/LinkageRules/type';
|
import { ActionType } from '../../../schema-settings/LinkageRules/type';
|
||||||
import { VariableOption, VariablesContextType } from '../../../variables/types';
|
import { VariableOption, VariablesContextType } from '../../../variables/types';
|
||||||
import { REGEX_OF_VARIABLE } from '../../../variables/utils/isVariable';
|
import { REGEX_OF_VARIABLE } from '../../../variables/utils/isVariable';
|
||||||
@ -19,7 +19,19 @@ interface Props {
|
|||||||
localVariables: VariableOption[];
|
localVariables: VariableOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const linkageMergeAction = async ({
|
/**
|
||||||
|
* 获取字段临时状态对象
|
||||||
|
*/
|
||||||
|
export async function getTempFieldState(condition: boolean | Promise<boolean>, value: any) {
|
||||||
|
[condition, value] = await Promise.all([condition, value]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
condition,
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const collectFieldStateOfLinkageRules = ({
|
||||||
operator,
|
operator,
|
||||||
value,
|
value,
|
||||||
field,
|
field,
|
||||||
@ -36,83 +48,79 @@ export const linkageMergeAction = async ({
|
|||||||
|
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
case ActionType.Required:
|
case ActionType.Required:
|
||||||
if (await conditionAnalyses({ rules: condition, variables, localVariables })) {
|
requiredResult.push(getTempFieldState(conditionAnalyses({ rules: condition, variables, localVariables }), true));
|
||||||
requiredResult.push(true);
|
|
||||||
}
|
|
||||||
field.linkageProperty = {
|
field.linkageProperty = {
|
||||||
...field.linkageProperty,
|
...field.linkageProperty,
|
||||||
required: requiredResult,
|
required: requiredResult,
|
||||||
};
|
};
|
||||||
field.required = last(field.linkageProperty.required);
|
|
||||||
break;
|
break;
|
||||||
case ActionType.InRequired:
|
case ActionType.InRequired:
|
||||||
if (await conditionAnalyses({ rules: condition, variables, localVariables })) {
|
requiredResult.push(getTempFieldState(conditionAnalyses({ rules: condition, variables, localVariables }), false));
|
||||||
requiredResult.push(false);
|
|
||||||
}
|
|
||||||
field.linkageProperty = {
|
field.linkageProperty = {
|
||||||
...field.linkageProperty,
|
...field.linkageProperty,
|
||||||
required: requiredResult,
|
required: requiredResult,
|
||||||
};
|
};
|
||||||
field.required = last(field.linkageProperty.required);
|
|
||||||
break;
|
break;
|
||||||
case ActionType.Visible:
|
case ActionType.Visible:
|
||||||
case ActionType.None:
|
case ActionType.None:
|
||||||
case ActionType.Hidden:
|
case ActionType.Hidden:
|
||||||
if (await conditionAnalyses({ rules: condition, variables, localVariables })) {
|
displayResult.push(
|
||||||
displayResult.push(operator);
|
getTempFieldState(conditionAnalyses({ rules: condition, variables, localVariables }), operator),
|
||||||
}
|
);
|
||||||
field.linkageProperty = {
|
field.linkageProperty = {
|
||||||
...field.linkageProperty,
|
...field.linkageProperty,
|
||||||
display: displayResult,
|
display: displayResult,
|
||||||
};
|
};
|
||||||
field.display = last(displayResult);
|
|
||||||
break;
|
break;
|
||||||
case ActionType.Editable:
|
case ActionType.Editable:
|
||||||
case ActionType.ReadOnly:
|
case ActionType.ReadOnly:
|
||||||
case ActionType.ReadPretty:
|
case ActionType.ReadPretty:
|
||||||
if (await conditionAnalyses({ rules: condition, variables, localVariables })) {
|
patternResult.push(
|
||||||
patternResult.push(operator);
|
getTempFieldState(conditionAnalyses({ rules: condition, variables, localVariables }), operator),
|
||||||
}
|
);
|
||||||
field.linkageProperty = {
|
field.linkageProperty = {
|
||||||
...field.linkageProperty,
|
...field.linkageProperty,
|
||||||
pattern: patternResult,
|
pattern: patternResult,
|
||||||
};
|
};
|
||||||
field.pattern = last(patternResult);
|
|
||||||
break;
|
break;
|
||||||
case ActionType.Value:
|
case ActionType.Value:
|
||||||
if (isConditionEmpty(condition) || (await conditionAnalyses({ rules: condition, variables, localVariables }))) {
|
{
|
||||||
if (value?.mode === 'express') {
|
const getValue = async () => {
|
||||||
if ((value.value || value.result) == null) {
|
if (value?.mode === 'express') {
|
||||||
return;
|
if ((value.value || value.result) == null) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 1. 解析如 `{{$user.name}}` 之类的变量
|
// 1. 解析如 `{{$user.name}}` 之类的变量
|
||||||
const { exp, scope: expScope } = await replaceVariables(value.value || value.result, {
|
const { exp, scope: expScope } = await replaceVariables(value.value || value.result, {
|
||||||
variables,
|
variables,
|
||||||
localVariables,
|
localVariables,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 2. TODO: 需要把里面解析变量的逻辑删除,因为在上一步已经解析过了
|
// 2. TODO: 需要把里面解析变量的逻辑删除,因为在上一步已经解析过了
|
||||||
const result = evaluate(exp, { ...values, now: () => new Date().toString(), ...expScope });
|
const result = evaluate(exp, { ...values, now: () => new Date().toString(), ...expScope });
|
||||||
valueResult.push(result);
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
}
|
||||||
|
} else if (value?.mode === 'constant') {
|
||||||
|
return value?.value || value;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
} else if (value?.mode === 'constant') {
|
};
|
||||||
valueResult.push(value?.value || value);
|
if (isConditionEmpty(condition)) {
|
||||||
|
valueResult.push(getTempFieldState(true, getValue()));
|
||||||
} else {
|
} else {
|
||||||
valueResult.push(null);
|
valueResult.push(
|
||||||
|
getTempFieldState(conditionAnalyses({ rules: condition, variables, localVariables }), getValue()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
field.linkageProperty = {
|
field.linkageProperty = {
|
||||||
...field.linkageProperty,
|
...field.linkageProperty,
|
||||||
value: valueResult,
|
value: valueResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (last(valueResult) !== undefined) {
|
|
||||||
field.value = last(valueResult);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
Loading…
Reference in New Issue
Block a user