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 { 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 { autorun, raw } from '@formily/reactive';
|
||||
import { autorun, toJS } from '@formily/reactive';
|
||||
import { uid } from '@formily/shared';
|
||||
import { ConfigProvider, Spin } from 'antd';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useActionContext } from '..';
|
||||
import { useAttach, useComponent } from '../..';
|
||||
import { ActionType } from '../../../schema-settings/LinkageRules/type';
|
||||
import { useLocalVariables, useVariables } from '../../../variables';
|
||||
import { useProps } from '../../hooks/useProps';
|
||||
import { linkageMergeAction } from './utils';
|
||||
import { collectFieldStateOfLinkageRules, getTempFieldState } from './utils';
|
||||
|
||||
export interface FormProps {
|
||||
[key: string]: any;
|
||||
@ -105,38 +106,64 @@ const WithForm = (props: WithFormProps) => {
|
||||
const disposes = [];
|
||||
|
||||
form.addEffects(id, () => {
|
||||
linkageRules.forEach((v, index) => {
|
||||
linkageRules.forEach((v) => {
|
||||
v.actions?.forEach((h) => {
|
||||
if (h.targetFields?.length) {
|
||||
const fields = h.targetFields.join(',');
|
||||
onFieldInit(`*(${fields})`, (field: any, form) => {
|
||||
field['initProperty'] = field?.['initProperty'] ?? {
|
||||
display: field.display,
|
||||
required: field.required,
|
||||
pattern: field.pattern,
|
||||
value: field.value || field.initialValue,
|
||||
};
|
||||
});
|
||||
onFieldChange(`*(${fields})`, ['value', 'required', 'pattern', 'display'], (field: any) => {
|
||||
field.linkageProperty = {
|
||||
display: field.linkageProperty?.display,
|
||||
display: getTempFieldState(true, field.display),
|
||||
required: getTempFieldState(true, field.required),
|
||||
pattern: getTempFieldState(true, field.pattern),
|
||||
value: getTempFieldState(true, field.value || field.initialValue),
|
||||
};
|
||||
});
|
||||
|
||||
// 之前使用的 `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 和 `autorun` 代替
|
||||
onFieldInit(`*(${fields})`, (field: any, form) => {
|
||||
field.linkageProperty = {};
|
||||
disposes.push(
|
||||
autorun(async () => {
|
||||
await linkageMergeAction({
|
||||
autorun(() => {
|
||||
// 当条件改变触发 autorun 时,会同步收集字段状态,并保存到 field.linkageProperty 中
|
||||
collectFieldStateOfLinkageRules({
|
||||
operator: h.operator,
|
||||
value: h.value,
|
||||
field,
|
||||
condition: v.condition,
|
||||
values: raw(form?.values),
|
||||
values: toJS(form?.values),
|
||||
variables,
|
||||
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' },
|
||||
);
|
||||
|
||||
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 { evaluators } from '@nocobase/evaluators/client';
|
||||
import { uid } from '@nocobase/utils/client';
|
||||
import _, { last } from 'lodash';
|
||||
import _ from 'lodash';
|
||||
import { ActionType } from '../../../schema-settings/LinkageRules/type';
|
||||
import { VariableOption, VariablesContextType } from '../../../variables/types';
|
||||
import { REGEX_OF_VARIABLE } from '../../../variables/utils/isVariable';
|
||||
@ -19,7 +19,19 @@ interface Props {
|
||||
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,
|
||||
value,
|
||||
field,
|
||||
@ -36,83 +48,79 @@ export const linkageMergeAction = async ({
|
||||
|
||||
switch (operator) {
|
||||
case ActionType.Required:
|
||||
if (await conditionAnalyses({ rules: condition, variables, localVariables })) {
|
||||
requiredResult.push(true);
|
||||
}
|
||||
requiredResult.push(getTempFieldState(conditionAnalyses({ rules: condition, variables, localVariables }), true));
|
||||
field.linkageProperty = {
|
||||
...field.linkageProperty,
|
||||
required: requiredResult,
|
||||
};
|
||||
field.required = last(field.linkageProperty.required);
|
||||
break;
|
||||
case ActionType.InRequired:
|
||||
if (await conditionAnalyses({ rules: condition, variables, localVariables })) {
|
||||
requiredResult.push(false);
|
||||
}
|
||||
requiredResult.push(getTempFieldState(conditionAnalyses({ rules: condition, variables, localVariables }), false));
|
||||
field.linkageProperty = {
|
||||
...field.linkageProperty,
|
||||
required: requiredResult,
|
||||
};
|
||||
field.required = last(field.linkageProperty.required);
|
||||
break;
|
||||
case ActionType.Visible:
|
||||
case ActionType.None:
|
||||
case ActionType.Hidden:
|
||||
if (await conditionAnalyses({ rules: condition, variables, localVariables })) {
|
||||
displayResult.push(operator);
|
||||
}
|
||||
displayResult.push(
|
||||
getTempFieldState(conditionAnalyses({ rules: condition, variables, localVariables }), operator),
|
||||
);
|
||||
field.linkageProperty = {
|
||||
...field.linkageProperty,
|
||||
display: displayResult,
|
||||
};
|
||||
field.display = last(displayResult);
|
||||
break;
|
||||
case ActionType.Editable:
|
||||
case ActionType.ReadOnly:
|
||||
case ActionType.ReadPretty:
|
||||
if (await conditionAnalyses({ rules: condition, variables, localVariables })) {
|
||||
patternResult.push(operator);
|
||||
}
|
||||
patternResult.push(
|
||||
getTempFieldState(conditionAnalyses({ rules: condition, variables, localVariables }), operator),
|
||||
);
|
||||
field.linkageProperty = {
|
||||
...field.linkageProperty,
|
||||
pattern: patternResult,
|
||||
};
|
||||
field.pattern = last(patternResult);
|
||||
break;
|
||||
case ActionType.Value:
|
||||
if (isConditionEmpty(condition) || (await conditionAnalyses({ rules: condition, variables, localVariables }))) {
|
||||
if (value?.mode === 'express') {
|
||||
if ((value.value || value.result) == null) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
const getValue = async () => {
|
||||
if (value?.mode === 'express') {
|
||||
if ((value.value || value.result) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 解析如 `{{$user.name}}` 之类的变量
|
||||
const { exp, scope: expScope } = await replaceVariables(value.value || value.result, {
|
||||
variables,
|
||||
localVariables,
|
||||
});
|
||||
// 1. 解析如 `{{$user.name}}` 之类的变量
|
||||
const { exp, scope: expScope } = await replaceVariables(value.value || value.result, {
|
||||
variables,
|
||||
localVariables,
|
||||
});
|
||||
|
||||
try {
|
||||
// 2. TODO: 需要把里面解析变量的逻辑删除,因为在上一步已经解析过了
|
||||
const result = evaluate(exp, { ...values, now: () => new Date().toString(), ...expScope });
|
||||
valueResult.push(result);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
try {
|
||||
// 2. TODO: 需要把里面解析变量的逻辑删除,因为在上一步已经解析过了
|
||||
const result = evaluate(exp, { ...values, now: () => new Date().toString(), ...expScope });
|
||||
return result;
|
||||
} catch (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 {
|
||||
valueResult.push(null);
|
||||
valueResult.push(
|
||||
getTempFieldState(conditionAnalyses({ rules: condition, variables, localVariables }), getValue()),
|
||||
);
|
||||
}
|
||||
|
||||
field.linkageProperty = {
|
||||
...field.linkageProperty,
|
||||
value: valueResult,
|
||||
};
|
||||
|
||||
if (last(valueResult) !== undefined) {
|
||||
field.value = last(valueResult);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
Loading…
Reference in New Issue
Block a user