fix(linkage rules) : support naming, enabling and disabling, copying, and assigning null values (#1511)
* fix: condiction suport empty * fix: condiction suport empty * fix: condiction suport empty * fix: condiction suport empty * feat: detailed operations support linkage rules * fix: button linkage failed during page change of detail block * fix: button linkage failed during page change of detail block * fix: linkage values not suport formula * fix: linkage value suport empty * fix: checkbox value linkage failed * refactor: using evaluators package * refactor: using evaluators package * refactor: linkage field * fix: relation field is limited to one level * fix: relation field is limited to one level * fix: linkage local * feat: support copy linkage rules * feat: support naming linkage rules * feat: support naming linkage rules * style: style improve * fix: missing operation when copying linkage * feat: linkage rules support disabling and enabling * fix: linkage rule update is not updated immediately * feat: action support linkage enabled * feat: action support linkage enabled * feat: action support linkage enabled * fix: linkage rules title update exception * feat: action support linkage enabled * fix: linkage rules copy failed * fix: linkage rules copy failed * fix: linkage rules copy failed * fix: linkage rules copy failed * refactor: code improve * refactor: code improve * fix: linkage rules copy failed * fix: linkage rules copy failed * fix: the first input is cleared * refactor: code improve * refactor: code improve * refactor: delete redundant variables * fix: action modal automatically closes * fix: action automatically closes * fix: action automatically closes * fix: details block chang page action linkage failed * fix: details block chang page action linkage failed * feat: syntax references * feat: syntax references * fix: current time * fix: current time * fix: action linkage * fix: formulajs tyy catch * fix: multi-level linkage * chore: linkage support * chore: linkage support * chore: linkage code improve --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
02758cb338
commit
c2885ee1d6
@ -126,6 +126,7 @@ export const useCollectionFilterOptions = (collectionName: string) => {
|
|||||||
operators?.filter?.((operator) => {
|
operators?.filter?.((operator) => {
|
||||||
return !operator?.visible || operator.visible(field);
|
return !operator?.visible || operator.visible(field);
|
||||||
}) || [],
|
}) || [],
|
||||||
|
interface: field.interface,
|
||||||
};
|
};
|
||||||
if (field.target && depth > 2) {
|
if (field.target && depth > 2) {
|
||||||
return;
|
return;
|
||||||
|
@ -52,6 +52,7 @@ export default {
|
|||||||
"Value":"Value",
|
"Value":"Value",
|
||||||
"Disabled":"Disabled",
|
"Disabled":"Disabled",
|
||||||
"Enabled":"Enabled",
|
"Enabled":"Enabled",
|
||||||
|
"Empty":"Empty",
|
||||||
"Linkage rule":"Linkage rule",
|
"Linkage rule":"Linkage rule",
|
||||||
"Linkage rules":"Linkage rules",
|
"Linkage rules":"Linkage rules",
|
||||||
"Condition":"Condition",
|
"Condition":"Condition",
|
||||||
|
@ -58,6 +58,7 @@ export default {
|
|||||||
"Value":"フィールド値",
|
"Value":"フィールド値",
|
||||||
"Disabled":"無効化",
|
"Disabled":"無効化",
|
||||||
"Enabled":"有効化",
|
"Enabled":"有効化",
|
||||||
|
"Empty":"くうきち",
|
||||||
"Linkage rule":"連動規則",
|
"Linkage rule":"連動規則",
|
||||||
"Linkage rules":"連動規則",
|
"Linkage rules":"連動規則",
|
||||||
"Condition":"条件#ジョウケン#",
|
"Condition":"条件#ジョウケン#",
|
||||||
|
@ -50,6 +50,7 @@ export default {
|
|||||||
"Value":"字段值",
|
"Value":"字段值",
|
||||||
"Disabled":"禁用",
|
"Disabled":"禁用",
|
||||||
"Enabled":"启用",
|
"Enabled":"启用",
|
||||||
|
"Empty":"赋空值",
|
||||||
"Linkage rule":"联动规则",
|
"Linkage rule":"联动规则",
|
||||||
"Linkage rules":"联动规则",
|
"Linkage rules":"联动规则",
|
||||||
"Condition":"条件",
|
"Condition":"条件",
|
||||||
|
@ -2,13 +2,14 @@ import { css } from '@emotion/css';
|
|||||||
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
|
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
import { Button, Modal, Popover } from 'antd';
|
import { Button, Modal, Popover } from 'antd';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useActionContext } from '../..';
|
import { useActionContext } from '../..';
|
||||||
|
import { useDesignable } from '../../';
|
||||||
import { Icon } from '../../../icon';
|
import { Icon } from '../../../icon';
|
||||||
|
import { useRecord } from '../../../record-provider';
|
||||||
import { SortableItem } from '../../common';
|
import { SortableItem } from '../../common';
|
||||||
import { useCompile, useComponent, useDesigner } from '../../hooks';
|
import { useCompile, useComponent, useDesigner } from '../../hooks';
|
||||||
import { useProps } from '../../hooks/useProps';
|
import { useProps } from '../../hooks/useProps';
|
||||||
import { useRecord } from '../../../record-provider';
|
|
||||||
import ActionContainer from './Action.Container';
|
import ActionContainer from './Action.Container';
|
||||||
import { ActionDesigner } from './Action.Designer';
|
import { ActionDesigner } from './Action.Designer';
|
||||||
import { ActionDrawer } from './Action.Drawer';
|
import { ActionDrawer } from './Action.Drawer';
|
||||||
@ -18,7 +19,6 @@ import { ActionPage } from './Action.Page';
|
|||||||
import { ActionContext } from './context';
|
import { ActionContext } from './context';
|
||||||
import { useA } from './hooks';
|
import { useA } from './hooks';
|
||||||
import { ComposedAction } from './types';
|
import { ComposedAction } from './types';
|
||||||
import { useDesignable } from '../../';
|
|
||||||
import { linkageAction } from './utils';
|
import { linkageAction } from './utils';
|
||||||
|
|
||||||
export const actionDesignerCss = css`
|
export const actionDesignerCss = css`
|
||||||
@ -96,11 +96,13 @@ export const Action: ComposedAction = observer((props: any) => {
|
|||||||
const disabled = form.disabled || field.disabled;
|
const disabled = form.disabled || field.disabled;
|
||||||
const openSize = fieldSchema?.['x-component-props']?.['openSize'];
|
const openSize = fieldSchema?.['x-component-props']?.['openSize'];
|
||||||
const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
|
const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
|
||||||
const { designable, } = useDesignable();
|
const { designable } = useDesignable();
|
||||||
const tarComponent = useComponent(component) || component;
|
const tarComponent = useComponent(component) || component;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
field.linkageProperty = {};
|
field.linkageProperty = {};
|
||||||
linkageRules.map((v) => {
|
linkageRules
|
||||||
|
.filter((k) => !k.disabled)
|
||||||
|
.map((v) => {
|
||||||
return v.actions?.map((h) => {
|
return v.actions?.map((h) => {
|
||||||
linkageAction(h.operator, field, v.condition, values);
|
linkageAction(h.operator, field, v.condition, values);
|
||||||
});
|
});
|
||||||
|
@ -69,7 +69,8 @@ const WithForm = (props) => {
|
|||||||
const { form } = props;
|
const { form } = props;
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { setFormValueChanged } = useActionContext();
|
const { setFormValueChanged } = useActionContext();
|
||||||
const linkageRules = getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'] || [];
|
const linkageRules =
|
||||||
|
(getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || [];
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = uid();
|
const id = uid();
|
||||||
form.addEffects(id, () => {
|
form.addEffects(id, () => {
|
||||||
@ -100,7 +101,9 @@ const WithForm = (props) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
onFieldChange(`*(${fields})`, ['value', 'required', 'pattern', 'display'], (field: any) => {
|
onFieldChange(`*(${fields})`, ['value', 'required', 'pattern', 'display'], (field: any) => {
|
||||||
field.linkageProperty = {};
|
field.linkageProperty = {
|
||||||
|
display: field.linkageProperty?.display,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,47 +1,13 @@
|
|||||||
import { last, get } from 'lodash';
|
import { last, cloneDeep } from 'lodash';
|
||||||
import * as functions from '@formulajs/formulajs';
|
|
||||||
import { conditionAnalyse } from '../../common/utils/uitls';
|
import { conditionAnalyse } from '../../common/utils/uitls';
|
||||||
import { ActionType } from '../../../schema-settings/LinkageRules/type';
|
import { ActionType } from '../../../schema-settings/LinkageRules/type';
|
||||||
|
import evaluators from '@nocobase/evaluators/client';
|
||||||
function now() {
|
|
||||||
return new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
const fnNames = Object.keys(functions).filter((key) => key !== 'default');
|
|
||||||
const fns = fnNames.map((key) => functions[key]);
|
|
||||||
function formula(expression: string, scope = {}) {
|
|
||||||
try {
|
|
||||||
const fn = new Function(...fnNames, ...Object.keys(scope), `return ${expression}`);
|
|
||||||
const result = fn(...fns, ...Object.values(scope));
|
|
||||||
if (typeof result === 'number') {
|
|
||||||
if (Number.isNaN(result) || !Number.isFinite(result)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return functions.ROUND(result, 9);
|
|
||||||
}
|
|
||||||
if (typeof result === 'function') {
|
|
||||||
return result();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function evaluate(expression: string, scope = {}) {
|
|
||||||
const mergeScope = { ...scope, now };
|
|
||||||
const exp = expression.trim().replace(/{{\s*([^{}]+)\s*}}/g, (_, v) => {
|
|
||||||
const item: any = get(scope, v);
|
|
||||||
const key = v.replace(/\.(\d+)/g, '["$1"]');
|
|
||||||
return ` ${typeof item === 'function' ? item() : key} `;
|
|
||||||
});
|
|
||||||
return formula(exp, mergeScope);
|
|
||||||
}
|
|
||||||
export const linkageMergeAction = ({ operator, value }, field, condition, values) => {
|
export const linkageMergeAction = ({ operator, value }, field, condition, values) => {
|
||||||
const requiredResult = field?.linkageProperty?.required || [field?.initProperty?.required || false];
|
const requiredResult = field?.linkageProperty?.required || [field?.initProperty?.required || false];
|
||||||
const displayResult = field?.linkageProperty?.display || [field?.initProperty?.display];
|
const displayResult = field?.linkageProperty?.display || [field?.initProperty?.display];
|
||||||
const patternResult = field?.linkageProperty?.pattern || [field?.initProperty?.pattern];
|
const patternResult = field?.linkageProperty?.pattern || [field?.initProperty?.pattern];
|
||||||
const valueResult = field?.linkageProperty?.value || [field.value || field?.initProperty?.value];
|
const valueResult = field?.linkageProperty?.value || [field.value || field?.initProperty?.value];
|
||||||
|
const { evaluate } = evaluators.get('formula.js');
|
||||||
|
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
case ActionType.Required:
|
case ActionType.Required:
|
||||||
@ -91,17 +57,23 @@ export const linkageMergeAction = ({ operator, value }, field, condition, values
|
|||||||
case ActionType.Value:
|
case ActionType.Value:
|
||||||
if (conditionAnalyse(condition, values)) {
|
if (conditionAnalyse(condition, values)) {
|
||||||
if (value?.mode === 'express') {
|
if (value?.mode === 'express') {
|
||||||
const result = evaluate(value.result || value.value, values);
|
const scope = cloneDeep(values);
|
||||||
|
try {
|
||||||
|
const result = evaluate(value.result || value.value, { ...scope, now: () => new Date().toString() });
|
||||||
valueResult.push(result);
|
valueResult.push(result);
|
||||||
} else {
|
} catch (error) {
|
||||||
|
}
|
||||||
|
} else if (value?.mode === 'constant') {
|
||||||
valueResult.push(value?.value || value);
|
valueResult.push(value?.value || value);
|
||||||
|
} else {
|
||||||
|
valueResult.push(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
field.linkageProperty = {
|
field.linkageProperty = {
|
||||||
...field.linkageProperty,
|
...field.linkageProperty,
|
||||||
value: valueResult,
|
value: valueResult,
|
||||||
};
|
};
|
||||||
setTimeout(() => (field.value = last(valueResult) === undefined ? field.value : last(valueResult)));
|
field.value = last(valueResult) === undefined ? field.value : last(valueResult);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
@ -39,7 +39,10 @@ http://ricostacruz.com/cheatsheets/umdjs.html
|
|||||||
var jsonLogic = {};
|
var jsonLogic = {};
|
||||||
var operations = {
|
var operations = {
|
||||||
$is: function (a, b) {
|
$is: function (a, b) {
|
||||||
return a == b;
|
return a === b;
|
||||||
|
},
|
||||||
|
$match: function (a, b) {
|
||||||
|
return JSON.stringify(a) === JSON.stringify(b);
|
||||||
},
|
},
|
||||||
$eq: function (a, b) {
|
$eq: function (a, b) {
|
||||||
return a === b;
|
return a === b;
|
||||||
@ -91,6 +94,12 @@ http://ricostacruz.com/cheatsheets/umdjs.html
|
|||||||
if (!a || typeof a.indexOf === 'undefined') return false;
|
if (!a || typeof a.indexOf === 'undefined') return false;
|
||||||
return !(a.indexOf(b) !== -1);
|
return !(a.indexOf(b) !== -1);
|
||||||
},
|
},
|
||||||
|
$isTruly: function (a) {
|
||||||
|
return a === true || a === 1;
|
||||||
|
},
|
||||||
|
$isFalsy: function (a) {
|
||||||
|
return !jsonLogic.truthy(a);
|
||||||
|
},
|
||||||
cat: function () {
|
cat: function () {
|
||||||
return Array.prototype.join.call(arguments, '');
|
return Array.prototype.join.call(arguments, '');
|
||||||
},
|
},
|
||||||
|
@ -1,30 +1,21 @@
|
|||||||
import { every, some, get } from 'lodash';
|
import { every, some, findIndex } from 'lodash';
|
||||||
import flat from 'flat';
|
import flat from 'flat';
|
||||||
import jsonLogic from '../../common/utils/logic';
|
import jsonLogic from '../../common/utils/logic';
|
||||||
|
|
||||||
function getDeepestProperty(obj) {
|
function getInnermostKeyAndValue(obj) {
|
||||||
let deepest = {
|
if (typeof obj !== 'object' || obj === null) {
|
||||||
operator: null,
|
return null;
|
||||||
value: null,
|
}
|
||||||
};
|
|
||||||
let deepestLevel = 0;
|
|
||||||
function traverse(obj, level) {
|
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
if (obj.hasOwnProperty(key)) {
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
if (typeof obj[key] === 'object') {
|
if (Object.prototype.toString.call(obj[key]) === '[object Object]' && obj[key] !== null) {
|
||||||
traverse(obj[key], level + 1);
|
return getInnermostKeyAndValue(obj[key]);
|
||||||
} else {
|
} else {
|
||||||
if (level > deepestLevel) {
|
return { key, value: obj[key] };
|
||||||
deepestLevel = level;
|
|
||||||
deepest.operator = key;
|
|
||||||
deepest.value = obj[key];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return null;
|
||||||
}
|
|
||||||
traverse(obj, 1);
|
|
||||||
return deepest;
|
|
||||||
}
|
}
|
||||||
const getValue = (str, values) => {
|
const getValue = (str, values) => {
|
||||||
const regex = /{{(.*?)}}/;
|
const regex = /{{(.*?)}}/;
|
||||||
@ -40,20 +31,47 @@ const getVariableValue = (str, values) => {
|
|||||||
const match = regex.exec(str);
|
const match = regex.exec(str);
|
||||||
return values[match?.[1]];
|
return values[match?.[1]];
|
||||||
};
|
};
|
||||||
|
const getTargetField = (obj) => {
|
||||||
|
const keys = getAllKeys(obj);
|
||||||
|
const index = findIndex(keys, (key, index, keys) => {
|
||||||
|
if (key.includes('$') && index > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const result = keys.slice(0, index);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getAllKeys(obj) {
|
||||||
|
const keys = [];
|
||||||
|
function traverse(o) {
|
||||||
|
Object.keys(o)
|
||||||
|
.sort()
|
||||||
|
.forEach(function (key) {
|
||||||
|
keys.push(key);
|
||||||
|
if (o[key] && typeof o[key] === 'object') {
|
||||||
|
traverse(o[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
traverse(obj);
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
export const conditionAnalyse = (rules, values) => {
|
export const conditionAnalyse = (rules, values) => {
|
||||||
const type = Object.keys(rules)[0] || '$and';
|
const type = Object.keys(rules)[0] || '$and';
|
||||||
const conditions = rules[type];
|
const conditions = rules[type];
|
||||||
const results = conditions.map((c) => {
|
const results = conditions.map((c) => {
|
||||||
const jsonlogic = getDeepestProperty(c);
|
const jsonlogic = getInnermostKeyAndValue(c);
|
||||||
const operator = jsonlogic.operator;
|
const operator = jsonlogic?.key;
|
||||||
const value = getValue(jsonlogic.value, values);
|
const value = getValue(jsonlogic?.value, values);
|
||||||
const targetField = Object.keys(flat(c))[0]?.replace?.(`.${operator}`, '');
|
const targetField = getTargetField(c);
|
||||||
if (!operator) {
|
if (!operator) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = jsonLogic.apply({ [operator]: [flat(values)?.[targetField], value] });
|
const currentValue = targetField.length > 1 ? flat(values)?.[targetField.join('.')] : values?.[targetField[0]];
|
||||||
|
const result = jsonLogic.apply({ [operator]: [currentValue, value] });
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { CloseCircleOutlined } from '@ant-design/icons';
|
import { CloseCircleOutlined } from '@ant-design/icons';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { observer } from '@formily/react';
|
import { observer, useField } from '@formily/react';
|
||||||
import { TreeSelect } from '@formily/antd';
|
import { TreeSelect } from '@formily/antd';
|
||||||
import { Select, Space } from 'antd';
|
import { Select, Space } from 'antd';
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
import { useCompile } from '../..';
|
import { useCompile } from '../..';
|
||||||
import { RemoveActionContext } from './context';
|
import { RemoveActionContext, LinkageLogicContext } from './context';
|
||||||
import { DynamicComponent } from './DynamicComponent';
|
import { DynamicComponent } from './DynamicComponent';
|
||||||
import { useValues } from './useValues';
|
import { useValues } from './useValues';
|
||||||
import { ActionType } from './type';
|
import { ActionType } from './type';
|
||||||
@ -28,6 +29,7 @@ export const FormFieldLinkageRuleAction = observer((props: any) => {
|
|||||||
operators,
|
operators,
|
||||||
} = useValues(options);
|
} = useValues(options);
|
||||||
return (
|
return (
|
||||||
|
<LinkageLogicContext.Provider value={uid()}>
|
||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
<Space
|
<Space
|
||||||
className={css`
|
className={css`
|
||||||
@ -51,7 +53,6 @@ export const FormFieldLinkageRuleAction = observer((props: any) => {
|
|||||||
allowClear
|
allowClear
|
||||||
treeData={compile(fields)}
|
treeData={compile(fields)}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
console.log(value)
|
|
||||||
setDataIndex(value);
|
setDataIndex(value);
|
||||||
}}
|
}}
|
||||||
placeholder={t('Select Field')}
|
placeholder={t('Select Field')}
|
||||||
@ -82,6 +83,7 @@ export const FormFieldLinkageRuleAction = observer((props: any) => {
|
|||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
</LinkageLogicContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,25 +1,37 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Input, Select } from 'antd';
|
import { Input, Select } from 'antd';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DynamicComponent } from './DynamicComponent';
|
import { DynamicComponent } from './DynamicComponent';
|
||||||
const { Option } = Select;
|
|
||||||
import { Variable } from '.././../schema-component';
|
import { Variable } from '.././../schema-component';
|
||||||
import { useVariableOptions } from './Variables';
|
import { useVariableOptions } from './Variables';
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
export const ValueDynamicComponent = (props) => {
|
export const ValueDynamicComponent = (props) => {
|
||||||
const { fieldValue, schema, setValue, collectionName } = props;
|
const { fieldValue, schema, setValue, collectionName } = props;
|
||||||
const [mode, setMode] = useState(fieldValue?.mode || 'constant');
|
const [mode, setMode] = useState(fieldValue?.mode || 'constant');
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const scope = useVariableOptions(collectionName);
|
const scope = useVariableOptions(collectionName);
|
||||||
return (
|
return (
|
||||||
<Input.Group compact style={{ minWidth: 280 }}>
|
<Input.Group compact>
|
||||||
<Select value={mode} style={{ width: 150 }} onChange={(value) => setMode(value)}>
|
<Select
|
||||||
|
value={mode}
|
||||||
|
style={{ width: 150 }}
|
||||||
|
onChange={(value) => {
|
||||||
|
setMode(value);
|
||||||
|
setValue({
|
||||||
|
mode: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Option value="constant">{t('Constant value')}</Option>
|
<Option value="constant">{t('Constant value')}</Option>
|
||||||
<Option value="express">{t('Expression')}</Option>
|
<Option value="express">{t('Expression')}</Option>
|
||||||
|
<Option value="empty">{t('Empty')}</Option>
|
||||||
</Select>
|
</Select>
|
||||||
<div style={{ minWidth: 150, maxWidth: 430 }}>
|
|
||||||
{mode === 'constant' ? (
|
{mode === 'constant' ? (
|
||||||
React.createElement(DynamicComponent, {
|
<div style={{ minWidth: 150, maxWidth: 430 }}>
|
||||||
|
{React.createElement(DynamicComponent, {
|
||||||
value: fieldValue?.value || fieldValue,
|
value: fieldValue?.value || fieldValue,
|
||||||
schema,
|
schema,
|
||||||
onChange(value) {
|
onChange(value) {
|
||||||
@ -28,8 +40,10 @@ export const ValueDynamicComponent = (props) => {
|
|||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
})
|
})}
|
||||||
) : (
|
</div>
|
||||||
|
) : mode === 'express' ? (
|
||||||
|
<div style={{ minWidth: 150, maxWidth: 430, fontSize: 13 }}>
|
||||||
<Variable.TextArea
|
<Variable.TextArea
|
||||||
value={fieldValue?.value}
|
value={fieldValue?.value}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
@ -43,8 +57,16 @@ export const ValueDynamicComponent = (props) => {
|
|||||||
scope={scope}
|
scope={scope}
|
||||||
style={{ minWidth: 460, marginRight: 15 }}
|
style={{ minWidth: 460, marginRight: 15 }}
|
||||||
/>
|
/>
|
||||||
)}
|
<>
|
||||||
|
<span style={{ marginLeft: '.25em' }} className={'ant-formily-item-extra'}>
|
||||||
|
{t('Syntax references')}:
|
||||||
|
</span>
|
||||||
|
<a href="https://formulajs.info/functions/" target="_blank">
|
||||||
|
Formula.js
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
</Input.Group>
|
</Input.Group>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -17,9 +17,12 @@ const supportsType = [
|
|||||||
'checkboxGroup',
|
'checkboxGroup',
|
||||||
'select',
|
'select',
|
||||||
'multipleSelect',
|
'multipleSelect',
|
||||||
|
'formula',
|
||||||
'oho',
|
'oho',
|
||||||
'obo',
|
'obo',
|
||||||
'm2o',
|
'm2o',
|
||||||
|
'o2m',
|
||||||
|
'm2m',
|
||||||
];
|
];
|
||||||
const useVariableTypes = (currentCollection) => {
|
const useVariableTypes = (currentCollection) => {
|
||||||
const { getCollectionFields, getInterface, getCollection } = useCollectionManager();
|
const { getCollectionFields, getInterface, getCollection } = useCollectionManager();
|
||||||
@ -45,10 +48,10 @@ const useVariableTypes = (currentCollection) => {
|
|||||||
schema: field?.uiSchema,
|
schema: field?.uiSchema,
|
||||||
value: field.name,
|
value: field.name,
|
||||||
};
|
};
|
||||||
if (field.target && depth > 2) {
|
if (field.target && depth > 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (depth > 2) {
|
if (depth > 1) {
|
||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
if (children?.length) {
|
if (children?.length) {
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Switch } from 'antd';
|
||||||
|
import { ArrayBase } from '@formily/antd';
|
||||||
|
|
||||||
|
export const EnableLinkage = React.forwardRef((props: any, ref) => {
|
||||||
|
const array = ArrayBase.useArray();
|
||||||
|
const index = ArrayBase.useIndex(props.index);
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
{...props}
|
||||||
|
checked={!array?.field?.value[index].disabled}
|
||||||
|
size={'small'}
|
||||||
|
style={{
|
||||||
|
transition: 'all 0.25s ease-in-out',
|
||||||
|
color: 'rgba(0, 0, 0, 0.8)',
|
||||||
|
fontSize: 16,
|
||||||
|
marginLeft: 6,
|
||||||
|
marginBottom: 3,
|
||||||
|
}}
|
||||||
|
onChange={(checked, e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
array.field.value.splice(index, 1, { ...array?.field?.value[index], disabled: !checked });
|
||||||
|
}}
|
||||||
|
onClick={(checked, e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,262 @@
|
|||||||
|
import React, { Fragment, useState, useEffect } from 'react';
|
||||||
|
import { Badge, Card, Collapse, CollapsePanelProps, CollapseProps, Empty, Input } from 'antd';
|
||||||
|
import { ArrayField } from '@formily/core';
|
||||||
|
import { RecursionField, useField, useFieldSchema, observer, ISchema } from '@formily/react';
|
||||||
|
import { toArr } from '@formily/shared';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { CopyOutlined } from '@ant-design/icons';
|
||||||
|
import { clone } from 'lodash';
|
||||||
|
import { ArrayBase, ArrayBaseMixins } from '@formily/antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const LinkageRulesTitle = (props) => {
|
||||||
|
const array = ArrayBase.useArray();
|
||||||
|
const index = ArrayBase.useIndex(props.index);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const value = array?.field?.value[index];
|
||||||
|
return (
|
||||||
|
<Input.TextArea
|
||||||
|
value={value.title || t('linkage rule')}
|
||||||
|
onChange={(ev) => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
array.field.value.splice(index, 1, { ...value, title: ev.target.value });
|
||||||
|
}}
|
||||||
|
onBlur={(ev) => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
array.field.value.splice(index, 1, { ...value, title: ev.target.value });
|
||||||
|
}}
|
||||||
|
autoSize
|
||||||
|
style={{ width: '70%', border: 'none' }}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IArrayCollapseProps extends CollapseProps {
|
||||||
|
defaultOpenPanelCount?: number;
|
||||||
|
}
|
||||||
|
type ComposedArrayCollapse = React.FC<React.PropsWithChildren<IArrayCollapseProps>> &
|
||||||
|
ArrayBaseMixins & {
|
||||||
|
CollapsePanel?: React.FC<React.PropsWithChildren<CollapsePanelProps>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAdditionComponent = (schema: ISchema) => {
|
||||||
|
return schema['x-component']?.indexOf?.('Addition') > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isIndexComponent = (schema: ISchema) => {
|
||||||
|
return schema['x-component']?.indexOf?.('Index') > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isRemoveComponent = (schema: ISchema) => {
|
||||||
|
return schema['x-component']?.indexOf?.('Remove') > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isMoveUpComponent = (schema: ISchema) => {
|
||||||
|
return schema['x-component']?.indexOf?.('MoveUp') > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isMoveDownComponent = (schema: ISchema) => {
|
||||||
|
return schema['x-component']?.indexOf?.('MoveDown') > -1;
|
||||||
|
};
|
||||||
|
const isCopyComponent = (schema: ISchema) => {
|
||||||
|
return schema['x-component']?.indexOf?.('Copy') > -1;
|
||||||
|
};
|
||||||
|
const isOperationComponent = (schema: ISchema) => {
|
||||||
|
return (
|
||||||
|
isAdditionComponent(schema) ||
|
||||||
|
isRemoveComponent(schema) ||
|
||||||
|
isMoveDownComponent(schema) ||
|
||||||
|
isMoveUpComponent(schema) ||
|
||||||
|
isCopyComponent(schema)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const range = (count: number) => Array.from({ length: count }).map((_, i) => i);
|
||||||
|
|
||||||
|
const takeDefaultActiveKeys = (dataSourceLength: number, defaultOpenPanelCount: number) => {
|
||||||
|
if (dataSourceLength < defaultOpenPanelCount) return range(dataSourceLength);
|
||||||
|
return range(defaultOpenPanelCount);
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertActiveKeys = (activeKeys: number[], index: number) => {
|
||||||
|
if (activeKeys.length <= index) return activeKeys.concat(index);
|
||||||
|
return activeKeys.reduce((buf, key) => {
|
||||||
|
if (key < index) return buf.concat(key);
|
||||||
|
if (key === index) return buf.concat([key, key + 1]);
|
||||||
|
return buf.concat(key + 1);
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ArrayCollapse: ComposedArrayCollapse = observer((props: IArrayCollapseProps) => {
|
||||||
|
const field = useField<ArrayField>();
|
||||||
|
const dataSource = Array.isArray(field.value) ? field.value : [];
|
||||||
|
const [activeKeys, setActiveKeys] = useState<number[]>(
|
||||||
|
takeDefaultActiveKeys(dataSource.length, props.defaultOpenPanelCount),
|
||||||
|
);
|
||||||
|
const schema = useFieldSchema();
|
||||||
|
const prefixCls = 'ant-formily-array-collapse';
|
||||||
|
useEffect(() => {
|
||||||
|
if (!field.modified && dataSource.length) {
|
||||||
|
setActiveKeys(takeDefaultActiveKeys(dataSource.length, props.defaultOpenPanelCount));
|
||||||
|
}
|
||||||
|
}, [dataSource.length, field]);
|
||||||
|
if (!schema) throw new Error('can not found schema object');
|
||||||
|
|
||||||
|
const renderAddition = () => {
|
||||||
|
return schema.reduceProperties((addition, schema, key) => {
|
||||||
|
if (isAdditionComponent(schema)) {
|
||||||
|
return <RecursionField schema={schema} name={key} />;
|
||||||
|
}
|
||||||
|
return addition;
|
||||||
|
}, null);
|
||||||
|
};
|
||||||
|
const renderEmpty = () => {
|
||||||
|
if (dataSource.length) return;
|
||||||
|
return (
|
||||||
|
<Card className={cls(`${prefixCls}-item`, props.className)}>
|
||||||
|
<Empty />
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderItems = () => {
|
||||||
|
return (
|
||||||
|
<Collapse
|
||||||
|
{...props}
|
||||||
|
activeKey={activeKeys}
|
||||||
|
onChange={(keys: string[]) => setActiveKeys(toArr(keys).map(Number))}
|
||||||
|
className={cls(`${prefixCls}-item`, props.className)}
|
||||||
|
>
|
||||||
|
{dataSource.map((item, index) => {
|
||||||
|
const items = Array.isArray(schema.items) ? schema.items[index] || schema.items[0] : schema.items;
|
||||||
|
|
||||||
|
const panelProps = field.query(`${field.address}.${index}`).get('componentProps');
|
||||||
|
const props: CollapsePanelProps = items['x-component-props'];
|
||||||
|
const header = () => {
|
||||||
|
const header = `${panelProps?.header || props.header || field.title}`;
|
||||||
|
const path = field.address.concat(index);
|
||||||
|
const errors = field.form.queryFeedbacks({
|
||||||
|
type: 'error',
|
||||||
|
address: `${path}.**`,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<ArrayBase.Item index={index} record={() => field.value?.[index]}>
|
||||||
|
<RecursionField
|
||||||
|
schema={items}
|
||||||
|
name={index}
|
||||||
|
filterProperties={(schema) => {
|
||||||
|
if (!isIndexComponent(schema)) return false;
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
onlyRenderProperties
|
||||||
|
/>
|
||||||
|
{errors.length ? (
|
||||||
|
<Badge size="small" className="errors-badge" count={errors.length}>
|
||||||
|
{header}
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<LinkageRulesTitle item={item.initialValue || item} index={index} />
|
||||||
|
)}
|
||||||
|
</ArrayBase.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const extra = (
|
||||||
|
<ArrayBase.Item index={index} record={item}>
|
||||||
|
<RecursionField
|
||||||
|
schema={items}
|
||||||
|
name={index}
|
||||||
|
filterProperties={(schema) => {
|
||||||
|
if (!isOperationComponent(schema)) return false;
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
onlyRenderProperties
|
||||||
|
/>
|
||||||
|
{panelProps?.extra}
|
||||||
|
</ArrayBase.Item>
|
||||||
|
);
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<RecursionField
|
||||||
|
schema={items}
|
||||||
|
name={index}
|
||||||
|
filterProperties={(schema) => {
|
||||||
|
if (isIndexComponent(schema)) return false;
|
||||||
|
if (isOperationComponent(schema)) return false;
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Collapse.Panel {...props} {...panelProps} forceRender key={index} header={header()} extra={extra}>
|
||||||
|
<ArrayBase.Item index={index} key={index} record={item}>
|
||||||
|
{content}
|
||||||
|
</ArrayBase.Item>
|
||||||
|
</Collapse.Panel>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Collapse>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ArrayBase
|
||||||
|
onAdd={(index) => {
|
||||||
|
setActiveKeys(insertActiveKeys(activeKeys, index));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderEmpty()}
|
||||||
|
{renderItems()}
|
||||||
|
{renderAddition()}
|
||||||
|
</ArrayBase>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const CollapsePanel: React.FC<React.PropsWithChildren<CollapsePanelProps>> = ({ children }) => {
|
||||||
|
return <Fragment>{children}</Fragment>;
|
||||||
|
};
|
||||||
|
|
||||||
|
CollapsePanel.displayName = 'CollapsePanel';
|
||||||
|
|
||||||
|
ArrayCollapse.defaultProps = {
|
||||||
|
defaultOpenPanelCount: 5,
|
||||||
|
};
|
||||||
|
ArrayCollapse.displayName = 'ArrayCollapse';
|
||||||
|
ArrayCollapse.CollapsePanel = CollapsePanel;
|
||||||
|
|
||||||
|
ArrayBase.mixin(ArrayCollapse);
|
||||||
|
|
||||||
|
export default ArrayCollapse;
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
ArrayCollapse.Copy = React.forwardRef((props: any, ref) => {
|
||||||
|
const self = useField();
|
||||||
|
const array = ArrayBase.useArray();
|
||||||
|
const index = ArrayBase.useIndex(props.index);
|
||||||
|
if (!array) return null;
|
||||||
|
if (array.field?.pattern !== 'editable') return null;
|
||||||
|
return (
|
||||||
|
<CopyOutlined
|
||||||
|
{...props}
|
||||||
|
style={{
|
||||||
|
transition: 'all 0.25s ease-in-out',
|
||||||
|
color: 'rgba(0, 0, 0, 0.8)',
|
||||||
|
fontSize: '16px',
|
||||||
|
marginLeft: 6,
|
||||||
|
}}
|
||||||
|
ref={ref}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (self?.disabled) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
if (array.props?.disabled) return;
|
||||||
|
const value = clone(array?.field?.value[index]);
|
||||||
|
array.field.push(value);
|
||||||
|
if (props.onClick) {
|
||||||
|
props.onClick(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
@ -12,4 +12,4 @@ export interface FilterContextProps {
|
|||||||
|
|
||||||
export const RemoveActionContext = createContext(null);
|
export const RemoveActionContext = createContext(null);
|
||||||
export const FilterContext = createContext<FilterContextProps>(null);
|
export const FilterContext = createContext<FilterContextProps>(null);
|
||||||
export const FilterLogicContext = createContext(null);
|
export const LinkageLogicContext = createContext(null);
|
@ -5,6 +5,8 @@ import { SchemaComponent } from '../../schema-component';
|
|||||||
import { FilterContext } from '../../schema-component/antd/filter/context';
|
import { FilterContext } from '../../schema-component/antd/filter/context';
|
||||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
|
import { FilterDynamicComponent } from './FilterDynamicComponent';
|
||||||
import { LinkageRuleActionGroup } from './LinkageRuleActionGroup';
|
import { LinkageRuleActionGroup } from './LinkageRuleActionGroup';
|
||||||
|
import { ArrayCollapse } from './components/LinkageHeader';
|
||||||
|
import { EnableLinkage } from './components/EnableLinkage';
|
||||||
|
|
||||||
export const FormLinkageRules = observer((props: any) => {
|
export const FormLinkageRules = observer((props: any) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
@ -13,6 +15,7 @@ export const FormLinkageRules = observer((props: any) => {
|
|||||||
return (
|
return (
|
||||||
<FilterContext.Provider value={{ field: options, fieldSchema, dynamicComponent, options: options || [] }}>
|
<FilterContext.Provider value={{ field: options, fieldSchema, dynamicComponent, options: options || [] }}>
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
|
components={{ ArrayCollapse }}
|
||||||
schema={{
|
schema={{
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@ -28,13 +31,9 @@ export const FormLinkageRules = observer((props: any) => {
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
'x-component': 'ArrayCollapse.CollapsePanel',
|
'x-component': 'ArrayCollapse.CollapsePanel',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
header: '{{ t("Linkage rule") }}',
|
extra: [<EnableLinkage />],
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
index: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'ArrayCollapse.Index',
|
|
||||||
},
|
|
||||||
layout: {
|
layout: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'FormLayout',
|
'x-component': 'FormLayout',
|
||||||
@ -91,6 +90,10 @@ export const FormLinkageRules = observer((props: any) => {
|
|||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'ArrayCollapse.MoveDown',
|
'x-component': 'ArrayCollapse.MoveDown',
|
||||||
},
|
},
|
||||||
|
copy: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayCollapse.Copy',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useField } from '@formily/react';
|
import { useField } from '@formily/react';
|
||||||
import { useContext, useEffect } from 'react';
|
import { useEffect, useContext } from 'react';
|
||||||
import { FilterLogicContext } from './context';
|
import { LinkageLogicContext } from './context';
|
||||||
|
|
||||||
const findOption = (dataIndex = [], options) => {
|
const findOption = (dataIndex = [], options) => {
|
||||||
let items = options;
|
let items = options;
|
||||||
@ -17,11 +17,11 @@ const findOption = (dataIndex = [], options) => {
|
|||||||
|
|
||||||
export const useValues = (options) => {
|
export const useValues = (options) => {
|
||||||
const field = useField<any>();
|
const field = useField<any>();
|
||||||
const logic = useContext(FilterLogicContext);
|
const logic = useContext(LinkageLogicContext);
|
||||||
|
|
||||||
const value2data = () => {
|
const value2data = () => {
|
||||||
field.data = { ...field.initialValue };
|
field.data = { ...(field.initialValue || field.value) };
|
||||||
const dataIndex = field.initialValue?.targetFields;
|
const dataIndex = field.data?.targetFields;
|
||||||
const option = (dataIndex && findOption(dataIndex, options)) || {};
|
const option = (dataIndex && findOption(dataIndex, options)) || {};
|
||||||
const operators = option?.operators || [];
|
const operators = option?.operators || [];
|
||||||
field.data.operators = operators;
|
field.data.operators = operators;
|
||||||
@ -36,9 +36,6 @@ export const useValues = (options) => {
|
|||||||
field.data = field.data || {};
|
field.data = field.data || {};
|
||||||
const operators = option?.operators;
|
const operators = option?.operators;
|
||||||
field.data.operators = operators?.filter((v) => {
|
field.data.operators = operators?.filter((v) => {
|
||||||
// if (dataIndex.length > 1) {
|
|
||||||
// return v.value !== 'value';
|
|
||||||
// }
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
field.data.schema = option?.schema;
|
field.data.schema = option?.schema;
|
||||||
|
@ -960,7 +960,9 @@ SchemaSettings.LinkageRules = (props) => {
|
|||||||
'x-component': FormLinkageRules,
|
'x-component': FormLinkageRules,
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
useProps: () => {
|
useProps: () => {
|
||||||
const options = useCollectionFilterOptions(collectionName);
|
const options = useCollectionFilterOptions(collectionName).filter(
|
||||||
|
(v) => !['o2m', 'm2m'].includes(v.interface),
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
options,
|
options,
|
||||||
defaultValues: gridSchema?.['x-linkage-rules'] || fieldSchema?.['x-linkage-rules'],
|
defaultValues: gridSchema?.['x-linkage-rules'] || fieldSchema?.['x-linkage-rules'],
|
||||||
|
@ -15,7 +15,7 @@ function appendArrayColumn(scope, key) {
|
|||||||
if (Array.isArray(data) && !isIndex && !data[path]) {
|
if (Array.isArray(data) && !isIndex && !data[path]) {
|
||||||
data[path] = data.map(item => item[path]);
|
data[path] = data.map(item => item[path]);
|
||||||
}
|
}
|
||||||
data = data[path];
|
data = data?.[path];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user