feat: support extending frontend filter operators (#6085)

* feat: operator extension

* fix: bug

* refactor: code improve

* fix: jsonLogic

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
Katherine 2025-02-15 11:14:36 +08:00 committed by GitHub
parent 3df4c0944d
commit 583ef1b98a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 269 additions and 223 deletions

View File

@ -2,9 +2,7 @@
"version": "1.6.0-alpha.25", "version": "1.6.0-alpha.25",
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,
"npmClientArgs": [ "npmClientArgs": ["--ignore-engines"],
"--ignore-engines"
],
"command": { "command": {
"version": { "version": {
"forcePublish": true, "forcePublish": true,

View File

@ -44,8 +44,14 @@ import type { CollectionFieldInterfaceFactory } from '../data-source';
import { OpenModeProvider } from '../modules/popup/OpenModeProvider'; import { OpenModeProvider } from '../modules/popup/OpenModeProvider';
import { AppSchemaComponentProvider } from './AppSchemaComponentProvider'; import { AppSchemaComponentProvider } from './AppSchemaComponentProvider';
import type { Plugin } from './Plugin'; import type { Plugin } from './Plugin';
import { getOperators } from './globalOperators';
import type { RequireJS } from './utils/requirejs'; import type { RequireJS } from './utils/requirejs';
type JsonLogic = {
addOperation: (name: string, fn?: any) => void;
rmOperation: (name: string) => void;
};
declare global { declare global {
interface Window { interface Window {
define: RequireJS['define']; define: RequireJS['define'];
@ -100,7 +106,7 @@ export class Application {
public dataSourceManager: DataSourceManager; public dataSourceManager: DataSourceManager;
public name: string; public name: string;
public globalVars: Record<string, any> = {}; public globalVars: Record<string, any> = {};
public jsonLogic: JsonLogic;
loading = true; loading = true;
maintained = false; maintained = false;
maintaining = false; maintaining = false;
@ -155,6 +161,7 @@ export class Application {
this.apiClient.auth.locale = lng; this.apiClient.auth.locale = lng;
}); });
this.initListeners(); this.initListeners();
this.jsonLogic = getOperators();
} }
private initListeners() { private initListeners() {
@ -488,4 +495,11 @@ export class Application {
getGlobalVar(key) { getGlobalVar(key) {
return get(this.globalVars, key); return get(this.globalVars, key);
} }
registerOperators(key, operator) {
this.jsonLogic[key] = operator;
}
getOperator(key) {
return this.jsonLogic[key];
}
} }

View File

@ -9,13 +9,11 @@
/* globals define,module */ /* globals define,module */
import dayjs from 'dayjs';
/* /*
Using a Universal Module Loader that should be browser, require, and AMD friendly Using a Universal Module Loader that should be browser, require, and AMD friendly
http://ricostacruz.com/cheatsheets/umdjs.html http://ricostacruz.com/cheatsheets/umdjs.html
*/ */
export function getJsonLogic() { export function getOperators() {
'use strict'; 'use strict';
/* globals console:false */ /* globals console:false */
@ -359,12 +357,12 @@ export function getJsonLogic() {
return !!value; return !!value;
}; };
jsonLogic.get_operator = function (logic) { jsonLogic.getOperator = function (logic) {
return Object.keys(logic)[0]; return Object.keys(logic)[0];
}; };
jsonLogic.get_values = function (logic) { jsonLogic.getValues = function (logic) {
return logic[jsonLogic.get_operator(logic)]; return logic[jsonLogic.getOperator(logic)];
}; };
jsonLogic.apply = function (logic, data) { jsonLogic.apply = function (logic, data) {
@ -379,7 +377,7 @@ export function getJsonLogic() {
return logic; return logic;
} }
var op = jsonLogic.get_operator(logic); var op = jsonLogic.getOperator(logic);
var values = logic[op]; var values = logic[op];
var i; var i;
var current; var current;
@ -543,7 +541,7 @@ export function getJsonLogic() {
var collection = []; var collection = [];
if (jsonLogic.is_logic(logic)) { if (jsonLogic.is_logic(logic)) {
var op = jsonLogic.get_operator(logic); var op = jsonLogic.getOperator(logic);
var values = logic[op]; var values = logic[op];
if (!Array.isArray(values)) { if (!Array.isArray(values)) {
@ -564,11 +562,11 @@ export function getJsonLogic() {
return arrayUnique(collection); return arrayUnique(collection);
}; };
jsonLogic.add_operation = function (name, code) { jsonLogic.addOperation = function (name, code) {
operations[name] = code; operations[name] = code;
}; };
jsonLogic.rm_operation = function (name) { jsonLogic.rmOperation = function (name) {
delete operations[name]; delete operations[name];
}; };
@ -593,8 +591,8 @@ export function getJsonLogic() {
if (jsonLogic.is_logic(pattern)) { if (jsonLogic.is_logic(pattern)) {
if (jsonLogic.is_logic(rule)) { if (jsonLogic.is_logic(rule)) {
var pattern_op = jsonLogic.get_operator(pattern); var pattern_op = jsonLogic.getOperator(pattern);
var rule_op = jsonLogic.get_operator(rule); var rule_op = jsonLogic.getOperator(rule);
if (pattern_op === '@' || pattern_op === rule_op) { if (pattern_op === '@' || pattern_op === rule_op) {
// echo "\nOperators match, go deeper\n"; // echo "\nOperators match, go deeper\n";

View File

@ -47,6 +47,7 @@ import { ActionContextProvider } from './context';
import { useGetAriaLabelOfAction } from './hooks/useGetAriaLabelOfAction'; import { useGetAriaLabelOfAction } from './hooks/useGetAriaLabelOfAction';
import { ActionContextProps, ActionProps, ComposedAction } from './types'; import { ActionContextProps, ActionProps, ComposedAction } from './types';
import { linkageAction, setInitialActionState } from './utils'; import { linkageAction, setInitialActionState } from './utils';
import { useApp } from '../../../application';
const useA = () => { const useA = () => {
return { return {
@ -95,7 +96,7 @@ export const Action: ComposedAction = withDynamicSchemaProps(
const { setSubmitted } = useActionContext(); const { setSubmitted } = useActionContext();
const { getAriaLabel } = useGetAriaLabelOfAction(title); const { getAriaLabel } = useGetAriaLabelOfAction(title);
const parentRecordData = useCollectionParentRecordData(); const parentRecordData = useCollectionParentRecordData();
const app = useApp();
useEffect(() => { useEffect(() => {
if (field.stateOfLinkageRules) { if (field.stateOfLinkageRules) {
setInitialActionState(field); setInitialActionState(field);
@ -105,13 +106,16 @@ export const Action: ComposedAction = withDynamicSchemaProps(
.filter((k) => !k.disabled) .filter((k) => !k.disabled)
.forEach((v) => { .forEach((v) => {
v.actions?.forEach((h) => { v.actions?.forEach((h) => {
linkageAction({ linkageAction(
{
operator: h.operator, operator: h.operator,
field, field,
condition: v.condition, condition: v.condition,
variables, variables,
localVariables, localVariables,
}); },
app.jsonLogic,
);
}); });
}); });
}, [field, linkageRules, localVariables, variables]); }, [field, linkageRules, localVariables, variables]);

View File

@ -80,7 +80,8 @@ export const requestSettingsSchema: ISchema = {
}, },
}; };
export const linkageAction = async ({ export const linkageAction = async (
{
operator, operator,
field, field,
condition, condition,
@ -92,13 +93,15 @@ export const linkageAction = async ({
condition; condition;
variables: VariablesContextType; variables: VariablesContextType;
localVariables: VariableOption[]; localVariables: VariableOption[];
}) => { },
jsonLogic: any,
) => {
const disableResult = field?.stateOfLinkageRules?.disabled || [false]; const disableResult = field?.stateOfLinkageRules?.disabled || [false];
const displayResult = field?.stateOfLinkageRules?.display || ['visible']; const displayResult = field?.stateOfLinkageRules?.display || ['visible'];
switch (operator) { switch (operator) {
case ActionType.Visible: case ActionType.Visible:
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables })) { if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic)) {
displayResult.push(operator); displayResult.push(operator);
field.data = field.data || {}; field.data = field.data || {};
field.data.hidden = false; field.data.hidden = false;
@ -110,7 +113,7 @@ export const linkageAction = async ({
field.display = last(displayResult); field.display = last(displayResult);
break; break;
case ActionType.Hidden: case ActionType.Hidden:
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables })) { if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic)) {
field.data = field.data || {}; field.data = field.data || {};
field.data.hidden = true; field.data.hidden = true;
} else { } else {
@ -119,7 +122,7 @@ export const linkageAction = async ({
} }
break; break;
case ActionType.Disabled: case ActionType.Disabled:
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables })) { if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic)) {
disableResult.push(true); disableResult.push(true);
} }
field.stateOfLinkageRules = { field.stateOfLinkageRules = {
@ -130,7 +133,7 @@ export const linkageAction = async ({
field.componentProps['disabled'] = last(disableResult); field.componentProps['disabled'] = last(disableResult);
break; break;
case ActionType.Active: case ActionType.Active:
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables })) { if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic)) {
disableResult.push(false); disableResult.push(false);
} else { } else {
disableResult.push(!!field.componentProps?.['disabled']); disableResult.push(!!field.componentProps?.['disabled']);

View File

@ -16,6 +16,7 @@ import { forEachLinkageRule } from '../../../../schema-settings/LinkageRules/for
import useLocalVariables from '../../../../variables/hooks/useLocalVariables'; import useLocalVariables from '../../../../variables/hooks/useLocalVariables';
import useVariables from '../../../../variables/hooks/useVariables'; import useVariables from '../../../../variables/hooks/useVariables';
import { useSubFormValue } from '../../association-field/hooks'; import { useSubFormValue } from '../../association-field/hooks';
import { useApp } from '../../../../application';
import { isSubMode } from '../../association-field/util'; import { isSubMode } from '../../association-field/util';
const isSubFormOrSubTableField = (fieldSchema: Schema) => { const isSubFormOrSubTableField = (fieldSchema: Schema) => {
@ -45,6 +46,7 @@ export const useLinkageRulesForSubTableOrSubForm = () => {
const variables = useVariables(); const variables = useVariables();
const linkageRules = getLinkageRules(schemaOfSubTableOrSubForm); const linkageRules = getLinkageRules(schemaOfSubTableOrSubForm);
const app = useApp();
useEffect(() => { useEffect(() => {
if (!isSubFormOrSubTableField(fieldSchema)) { if (!isSubFormOrSubTableField(fieldSchema)) {
@ -77,7 +79,8 @@ export const useLinkageRulesForSubTableOrSubForm = () => {
forEachLinkageRule(linkageRules, (action, rule) => { forEachLinkageRule(linkageRules, (action, rule) => {
if (action.targetFields?.includes(fieldSchema.name)) { if (action.targetFields?.includes(fieldSchema.name)) {
disposes.push( disposes.push(
bindLinkageRulesToFiled({ bindLinkageRulesToFiled(
{
field, field,
linkageRules, linkageRules,
formValues: formValue, formValues: formValue,
@ -86,7 +89,9 @@ export const useLinkageRulesForSubTableOrSubForm = () => {
rule, rule,
variables, variables,
variableNameOfLeftCondition: '$iteration', variableNameOfLeftCondition: '$iteration',
}), },
app.jsonLogic,
),
); );
} }
}); });

View File

@ -27,6 +27,7 @@ import { useToken } from '../../../style';
import { useLocalVariables, useVariables } from '../../../variables'; import { useLocalVariables, useVariables } from '../../../variables';
import { useProps } from '../../hooks/useProps'; import { useProps } from '../../hooks/useProps';
import { useFormBlockHeight } from './hook'; import { useFormBlockHeight } from './hook';
import { useApp } from '../../../application';
export interface FormProps extends IFormLayoutProps { export interface FormProps extends IFormLayoutProps {
form?: FormilyForm; form?: FormilyForm;
@ -136,6 +137,7 @@ const WithForm = (props: WithFormProps) => {
const localVariables = useLocalVariables({ currentForm: form }); const localVariables = useLocalVariables({ currentForm: form });
const { templateFinished } = useTemplateBlockContext(); const { templateFinished } = useTemplateBlockContext();
const { loading } = useDataBlockRequest() || {}; const { loading } = useDataBlockRequest() || {};
const app = useApp();
const linkageRules: any[] = const linkageRules: any[] =
(getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || []; (getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || [];
@ -175,7 +177,8 @@ const WithForm = (props: WithFormProps) => {
// 之前使用的 `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 和 `reaction` 代替 // 之前使用的 `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 和 `reaction` 代替
onFieldInit(`*(${fields})`, (field: any, form) => { onFieldInit(`*(${fields})`, (field: any, form) => {
disposes.push( disposes.push(
bindLinkageRulesToFiled({ bindLinkageRulesToFiled(
{
field, field,
linkageRules, linkageRules,
formValues: form.values, formValues: form.values,
@ -183,7 +186,9 @@ const WithForm = (props: WithFormProps) => {
action, action,
rule, rule,
variables, variables,
}), },
app.jsonLogic,
),
); );
}); });
} }

View File

@ -14,7 +14,7 @@ import { VariableOption, VariablesContextType } from '../../../variables/types';
import { isVariable } from '../../../variables/utils/isVariable'; import { isVariable } from '../../../variables/utils/isVariable';
import { transformVariableValue } from '../../../variables/utils/transformVariableValue'; import { transformVariableValue } from '../../../variables/utils/transformVariableValue';
import { inferPickerType } from '../../antd/date-picker/util'; import { inferPickerType } from '../../antd/date-picker/util';
import { getJsonLogic } from '../../common/utils/logic';
type VariablesCtx = { type VariablesCtx = {
/** 当前登录的用户 */ /** 当前登录的用户 */
$user?: Record<string, any>; $user?: Record<string, any>;
@ -76,7 +76,8 @@ function getAllKeys(obj) {
return keys; return keys;
} }
export const conditionAnalyses = async ({ export const conditionAnalyses = async (
{
ruleGroup, ruleGroup,
variables, variables,
localVariables, localVariables,
@ -90,17 +91,19 @@ export const conditionAnalyses = async ({
* @default '$nForm' * @default '$nForm'
*/ */
variableNameOfLeftCondition?: string; variableNameOfLeftCondition?: string;
}) => { },
jsonLogic: any,
) => {
const type = Object.keys(ruleGroup)[0] || '$and'; const type = Object.keys(ruleGroup)[0] || '$and';
const conditions = ruleGroup[type]; const conditions = ruleGroup[type];
let results = conditions.map(async (condition) => { let results = conditions.map(async (condition) => {
if ('$and' in condition || '$or' in condition) { if ('$and' in condition || '$or' in condition) {
return await conditionAnalyses({ ruleGroup: condition, variables, localVariables }); return await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic);
} }
const jsonlogic = getInnermostKeyAndValue(condition); const logicCalculation = getInnermostKeyAndValue(condition);
const operator = jsonlogic?.key; const operator = logicCalculation?.key;
if (!operator) { if (!operator) {
return true; return true;
@ -113,12 +116,11 @@ export const conditionAnalyses = async ({
}) })
.then(({ value }) => value); .then(({ value }) => value);
const parsingResult = isVariable(jsonlogic?.value) const parsingResult = isVariable(logicCalculation?.value)
? [variables.parseVariable(jsonlogic?.value, localVariables).then(({ value }) => value), targetValue] ? [variables.parseVariable(logicCalculation?.value, localVariables).then(({ value }) => value), targetValue]
: [jsonlogic?.value, targetValue]; : [logicCalculation?.value, targetValue];
try { try {
const jsonLogic = getJsonLogic();
const [value, targetValue] = await Promise.all(parsingResult); const [value, targetValue] = await Promise.all(parsingResult);
const targetCollectionField = await variables.getCollectionField(targetVariableName, localVariables); const targetCollectionField = await variables.getCollectionField(targetVariableName, localVariables);
let currentInputValue = transformVariableValue(targetValue, { targetCollectionField }); let currentInputValue = transformVariableValue(targetValue, { targetCollectionField });

View File

@ -22,6 +22,7 @@ import { linkageAction } from '../../schema-component/antd/action/utils';
import { usePopupUtils } from '../../schema-component/antd/page/pagePopupUtils'; import { usePopupUtils } from '../../schema-component/antd/page/pagePopupUtils';
import { parseVariables } from '../../schema-component/common/utils/uitls'; import { parseVariables } from '../../schema-component/common/utils/uitls';
import { useLocalVariables, useVariables } from '../../variables'; import { useLocalVariables, useVariables } from '../../variables';
import { useApp } from '../../application';
export function useAclCheck(actionPath) { export function useAclCheck(actionPath) {
const aclCheck = useAclCheckFn(); const aclCheck = useAclCheckFn();
@ -73,6 +74,7 @@ const InternalCreateRecordAction = (props: any, ref) => {
const { openPopup } = usePopupUtils(); const { openPopup } = usePopupUtils();
const treeRecordData = useTreeParentRecord(); const treeRecordData = useTreeParentRecord();
const cm = useCollectionManager(); const cm = useCollectionManager();
const app = useApp();
useEffect(() => { useEffect(() => {
field.stateOfLinkageRules = {}; field.stateOfLinkageRules = {};
@ -80,13 +82,16 @@ const InternalCreateRecordAction = (props: any, ref) => {
.filter((k) => !k.disabled) .filter((k) => !k.disabled)
.forEach((v) => { .forEach((v) => {
v.actions?.forEach((h) => { v.actions?.forEach((h) => {
linkageAction({ linkageAction(
{
operator: h.operator, operator: h.operator,
field, field,
condition: v.condition, condition: v.condition,
variables, variables,
localVariables, localVariables,
}); },
app.jsonLogic,
);
}); });
}); });
}, [field, linkageRules, localVariables, variables]); }, [field, linkageRules, localVariables, variables]);
@ -143,7 +148,6 @@ export const CreateAction = observer(
const form = useForm(); const form = useForm();
const variables = useVariables(); const variables = useVariables();
const aclCheck = useAclCheckFn(); const aclCheck = useAclCheckFn();
const enableChildren = fieldSchema['x-enable-children'] || []; const enableChildren = fieldSchema['x-enable-children'] || [];
const allowAddToCurrent = fieldSchema?.['x-allow-add-to-current']; const allowAddToCurrent = fieldSchema?.['x-allow-add-to-current'];
const linkageFromForm = fieldSchema?.['x-component-props']?.['linkageFromForm']; const linkageFromForm = fieldSchema?.['x-component-props']?.['linkageFromForm'];
@ -176,6 +180,7 @@ export const CreateAction = observer(
const compile = useCompile(); const compile = useCompile();
const { designable } = useDesignable(); const { designable } = useDesignable();
const icon = props.icon || null; const icon = props.icon || null;
const app = useApp();
const menuItems = useMemo<MenuProps['items']>(() => { const menuItems = useMemo<MenuProps['items']>(() => {
return inheritsCollections.map((option) => ({ return inheritsCollections.map((option) => ({
key: option.name, key: option.name,
@ -196,13 +201,16 @@ export const CreateAction = observer(
.filter((k) => !k.disabled) .filter((k) => !k.disabled)
.forEach((v) => { .forEach((v) => {
v.actions?.forEach((h) => { v.actions?.forEach((h) => {
linkageAction({ linkageAction(
{
operator: h.operator, operator: h.operator,
field, field,
condition: v.condition, condition: v.condition,
variables, variables,
localVariables, localVariables,
}); },
app.jsonLogic,
);
}); });
}); });
}, [field, linkageRules, localVariables, variables]); }, [field, linkageRules, localVariables, variables]);

View File

@ -39,7 +39,8 @@ interface Props {
variableNameOfLeftCondition?: string; variableNameOfLeftCondition?: string;
} }
export function bindLinkageRulesToFiled({ export function bindLinkageRulesToFiled(
{
field, field,
linkageRules, linkageRules,
formValues, formValues,
@ -61,7 +62,9 @@ export function bindLinkageRulesToFiled({
* @default '$nForm' * @default '$nForm'
*/ */
variableNameOfLeftCondition?: string; variableNameOfLeftCondition?: string;
}) { },
jsonLogic: any,
) {
field['initStateOfLinkageRules'] = { field['initStateOfLinkageRules'] = {
display: field.initStateOfLinkageRules?.display || getTempFieldState(true, field.display), display: field.initStateOfLinkageRules?.display || getTempFieldState(true, field.display),
required: field.initStateOfLinkageRules?.required || getTempFieldState(true, field.required || false), required: field.initStateOfLinkageRules?.required || getTempFieldState(true, field.required || false),
@ -89,7 +92,7 @@ export function bindLinkageRulesToFiled({
.join(','); .join(',');
return result; return result;
}, },
getSubscriber({ action, field, rule, variables, localVariables, variableNameOfLeftCondition }), getSubscriber({ action, field, rule, variables, localVariables, variableNameOfLeftCondition }, jsonLogic),
{ fireImmediately: true, equals: _.isEqual }, { fireImmediately: true, equals: _.isEqual },
); );
} }
@ -176,7 +179,8 @@ function getVariableValue(variableString: string, localVariables: VariableOption
return getValuesByPath(ctx, getPath(variableString)); return getValuesByPath(ctx, getPath(variableString));
} }
function getSubscriber({ function getSubscriber(
{
action, action,
field, field,
rule, rule,
@ -194,10 +198,13 @@ function getSubscriber({
* @default '$nForm' * @default '$nForm'
*/ */
variableNameOfLeftCondition?: string; variableNameOfLeftCondition?: string;
}): (value: string, oldValue: string) => void { },
jsonLogic,
): (value: string, oldValue: string) => void {
return () => { return () => {
// 当条件改变触发 reaction 时,会同步收集字段状态,并保存到 field.stateOfLinkageRules 中 // 当条件改变触发 reaction 时,会同步收集字段状态,并保存到 field.stateOfLinkageRules 中
collectFieldStateOfLinkageRules({ collectFieldStateOfLinkageRules(
{
operator: action.operator, operator: action.operator,
value: action.value, value: action.value,
field, field,
@ -205,7 +212,9 @@ function getSubscriber({
variables, variables,
localVariables, localVariables,
variableNameOfLeftCondition, variableNameOfLeftCondition,
}); },
jsonLogic,
);
// 当条件改变时,有可能会触发多个 reaction所以这里需要延迟一下确保所有的 reaction 都执行完毕后, // 当条件改变时,有可能会触发多个 reaction所以这里需要延迟一下确保所有的 reaction 都执行完毕后,
// 再从 field.stateOfLinkageRules 中取值,因为此时 field.stateOfLinkageRules 中的值才是全的。 // 再从 field.stateOfLinkageRules 中取值,因为此时 field.stateOfLinkageRules 中的值才是全的。
@ -286,15 +295,10 @@ function getFieldNameByOperator(operator: ActionType) {
} }
} }
export const collectFieldStateOfLinkageRules = ({ export const collectFieldStateOfLinkageRules = (
operator, { operator, value, field, condition, variables, localVariables, variableNameOfLeftCondition }: Props,
value, jsonLogic: any,
field, ) => {
condition,
variables,
localVariables,
variableNameOfLeftCondition,
}: Props) => {
const requiredResult = field?.stateOfLinkageRules?.required || [field?.initStateOfLinkageRules?.required]; const requiredResult = field?.stateOfLinkageRules?.required || [field?.initStateOfLinkageRules?.required];
const displayResult = field?.stateOfLinkageRules?.display || [field?.initStateOfLinkageRules?.display]; const displayResult = field?.stateOfLinkageRules?.display || [field?.initStateOfLinkageRules?.display];
const patternResult = field?.stateOfLinkageRules?.pattern || [field?.initStateOfLinkageRules?.pattern]; const patternResult = field?.stateOfLinkageRules?.pattern || [field?.initStateOfLinkageRules?.pattern];
@ -304,14 +308,14 @@ export const collectFieldStateOfLinkageRules = ({
switch (operator) { switch (operator) {
case ActionType.Required: case ActionType.Required:
requiredResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), true)); requiredResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult, jsonLogic), true));
field.stateOfLinkageRules = { field.stateOfLinkageRules = {
...field.stateOfLinkageRules, ...field.stateOfLinkageRules,
required: requiredResult, required: requiredResult,
}; };
break; break;
case ActionType.InRequired: case ActionType.InRequired:
requiredResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), false)); requiredResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult, jsonLogic), false));
field.stateOfLinkageRules = { field.stateOfLinkageRules = {
...field.stateOfLinkageRules, ...field.stateOfLinkageRules,
required: requiredResult, required: requiredResult,
@ -320,7 +324,7 @@ export const collectFieldStateOfLinkageRules = ({
case ActionType.Visible: case ActionType.Visible:
case ActionType.None: case ActionType.None:
case ActionType.Hidden: case ActionType.Hidden:
displayResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), operator)); displayResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult, jsonLogic), operator));
field.stateOfLinkageRules = { field.stateOfLinkageRules = {
...field.stateOfLinkageRules, ...field.stateOfLinkageRules,
display: displayResult, display: displayResult,
@ -329,7 +333,7 @@ export const collectFieldStateOfLinkageRules = ({
case ActionType.Editable: case ActionType.Editable:
case ActionType.ReadOnly: case ActionType.ReadOnly:
case ActionType.ReadPretty: case ActionType.ReadPretty:
patternResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), operator)); patternResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult, jsonLogic), operator));
field.stateOfLinkageRules = { field.stateOfLinkageRules = {
...field.stateOfLinkageRules, ...field.stateOfLinkageRules,
pattern: patternResult, pattern: patternResult,
@ -364,7 +368,7 @@ export const collectFieldStateOfLinkageRules = ({
if (isConditionEmpty(condition)) { if (isConditionEmpty(condition)) {
valueResult.push(getTempFieldState(true, getValue())); valueResult.push(getTempFieldState(true, getValue()));
} else { } else {
valueResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), getValue())); valueResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult, jsonLogic), getValue()));
} }
field.stateOfLinkageRules = { field.stateOfLinkageRules = {
...field.stateOfLinkageRules, ...field.stateOfLinkageRules,

View File

@ -25,13 +25,13 @@ const getActionValue = (operator, value) => {
} }
}; };
const getSatisfiedActions = async ({ rules, variables, localVariables }) => { const getSatisfiedActions = async ({ rules, variables, localVariables }, jsonLogic) => {
const satisfiedRules = ( const satisfiedRules = (
await Promise.all( await Promise.all(
rules rules
.filter((k) => !k.disabled) .filter((k) => !k.disabled)
.map(async (rule) => { .map(async (rule) => {
if (await conditionAnalyses({ ruleGroup: rule.condition, variables, localVariables })) { if (await conditionAnalyses({ ruleGroup: rule.condition, variables, localVariables }, jsonLogic)) {
return rule; return rule;
} else return null; } else return null;
}), }),
@ -40,15 +40,15 @@ const getSatisfiedActions = async ({ rules, variables, localVariables }) => {
return satisfiedRules.map((rule) => rule.actions).flat(); return satisfiedRules.map((rule) => rule.actions).flat();
}; };
const getSatisfiedValues = async ({ rules, variables, localVariables }) => { const getSatisfiedValues = async ({ rules, variables, localVariables }, jsonLogic) => {
return (await getSatisfiedActions({ rules, variables, localVariables })).map((action) => ({ return (await getSatisfiedActions({ rules, variables, localVariables }, jsonLogic)).map((action) => ({
...action, ...action,
value: getActionValue(action.operator, action.value), value: getActionValue(action.operator, action.value),
})); }));
}; };
export const getSatisfiedValueMap = async ({ rules, variables, localVariables }) => { export const getSatisfiedValueMap = async ({ rules, variables, localVariables }, jsonLogic) => {
const values = await getSatisfiedValues({ rules, variables, localVariables }); const values = await getSatisfiedValues({ rules, variables, localVariables }, jsonLogic);
const valueMap = values.reduce((a, v) => ({ ...a, [v.operator]: v.value }), {}); const valueMap = values.reduce((a, v) => ({ ...a, [v.operator]: v.value }), {});
return valueMap; return valueMap;
}; };

View File

@ -15,7 +15,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { useLocalVariables, useVariables } from '../../variables'; import { useLocalVariables, useVariables } from '../../variables';
import { getSatisfiedValueMap } from './compute-rules'; import { getSatisfiedValueMap } from './compute-rules';
import { LinkageRuleCategory, LinkageRuleDataKeyMap } from './type'; import { LinkageRuleCategory, LinkageRuleDataKeyMap } from './type';
import { useApp } from '../../application';
export function useSatisfiedActionValues({ export function useSatisfiedActionValues({
formValues, formValues,
category = 'default', category = 'default',
@ -35,10 +35,11 @@ export function useSatisfiedActionValues({
const localVariables = useLocalVariables({ currentForm: { values: formValues } as any }); const localVariables = useLocalVariables({ currentForm: { values: formValues } as any });
const localSchema = schema ?? fieldSchema; const localSchema = schema ?? fieldSchema;
const styleRules = rules ?? localSchema[LinkageRuleDataKeyMap[category]]; const styleRules = rules ?? localSchema[LinkageRuleDataKeyMap[category]];
const app = useApp();
const compute = useCallback(() => { const compute = useCallback(() => {
if (styleRules && formValues) { if (styleRules && formValues) {
getSatisfiedValueMap({ rules: styleRules, variables, localVariables }) getSatisfiedValueMap({ rules: styleRules, variables, localVariables }, app.jsonLogic)
.then((valueMap) => { .then((valueMap) => {
if (!isEmpty(valueMap)) { if (!isEmpty(valueMap)) {
setValueMap(valueMap); setValueMap(valueMap);

View File

@ -121,9 +121,11 @@ const RuleTypes = {
number: t('Number', { ns: NAMESPACE }), number: t('Number', { ns: NAMESPACE }),
lowercase: t('Lowercase letters', { ns: NAMESPACE }), lowercase: t('Lowercase letters', { ns: NAMESPACE }),
uppercase: t('Uppercase letters', { ns: NAMESPACE }), uppercase: t('Uppercase letters', { ns: NAMESPACE }),
symbol: t('Symbols', { ns: NAMESPACE }) symbol: t('Symbols', { ns: NAMESPACE }),
}; };
return <code>{value?.map(charset => charsetLabels[charset]).join(', ') || t('Number', { ns: NAMESPACE })}</code>; return (
<code>{value?.map((charset) => charsetLabels[charset]).join(', ') || t('Number', { ns: NAMESPACE })}</code>
);
}, },
}, },
fieldset: { fieldset: {
@ -154,14 +156,14 @@ const RuleTypes = {
{ value: 'number', label: `{{t("Number", { ns: "${NAMESPACE}" })}}` }, { value: 'number', label: `{{t("Number", { ns: "${NAMESPACE}" })}}` },
{ value: 'lowercase', label: `{{t("Lowercase letters", { ns: "${NAMESPACE}" })}}` }, { value: 'lowercase', label: `{{t("Lowercase letters", { ns: "${NAMESPACE}" })}}` },
{ value: 'uppercase', label: `{{t("Uppercase letters", { ns: "${NAMESPACE}" })}}` }, { value: 'uppercase', label: `{{t("Uppercase letters", { ns: "${NAMESPACE}" })}}` },
{ value: 'symbol', label: `{{t("Symbols", { ns: "${NAMESPACE}" })}}` } { value: 'symbol', label: `{{t("Symbols", { ns: "${NAMESPACE}" })}}` },
], ],
required: true, required: true,
default: ['number'], default: ['number'],
'x-validator': { 'x-validator': {
minItems: 1, minItems: 1,
message: `{{t("At least one character set should be selected", { ns: "${NAMESPACE}" })}}` message: `{{t("At least one character set should be selected", { ns: "${NAMESPACE}" })}}`,
} },
}, },
}, },
defaults: { defaults: {

View File

@ -301,7 +301,7 @@ const CHAR_SETS = {
lowercase: 'abcdefghijklmnopqrstuvwxyz', lowercase: 'abcdefghijklmnopqrstuvwxyz',
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
// 符号只保留常用且安全的符号,有需要的可以自己加比如[]{}|;:,.<>放在链接或者文件名里容易出问题的字符 // 符号只保留常用且安全的符号,有需要的可以自己加比如[]{}|;:,.<>放在链接或者文件名里容易出问题的字符
symbol: '!@#$%^&*_-+' symbol: '!@#$%^&*_-+',
} as const; } as const;
interface RandomCharOptions { interface RandomCharOptions {
@ -317,21 +317,16 @@ sequencePatterns.register('randomChar', {
if (!options?.charsets || options.charsets.length === 0) { if (!options?.charsets || options.charsets.length === 0) {
return 'At least one character set should be selected'; return 'At least one character set should be selected';
} }
if (options.charsets.some(charset => !CHAR_SETS[charset])) { if (options.charsets.some((charset) => !CHAR_SETS[charset])) {
return 'Invalid charset selected'; return 'Invalid charset selected';
} }
return null; return null;
}, },
generate(instance: any, options: RandomCharOptions) { generate(instance: any, options: RandomCharOptions) {
const { const { length = 6, charsets = ['number'] } = options;
length = 6,
charsets = ['number']
} = options;
const chars = [...new Set( const chars = [...new Set(charsets.reduce((acc, charset) => acc + CHAR_SETS[charset], ''))];
charsets.reduce((acc, charset) => acc + CHAR_SETS[charset], '')
)];
const getRandomChar = () => { const getRandomChar = () => {
const randomIndex = Math.floor(Math.random() * chars.length); const randomIndex = Math.floor(Math.random() * chars.length);
@ -352,20 +347,27 @@ sequencePatterns.register('randomChar', {
}, },
getMatcher(options: RandomCharOptions) { getMatcher(options: RandomCharOptions) {
const pattern = [...new Set( const pattern = [
...new Set(
(options.charsets || ['number']).reduce((acc, charset) => { (options.charsets || ['number']).reduce((acc, charset) => {
switch (charset) { switch (charset) {
case 'number': return acc + '0-9'; case 'number':
case 'lowercase': return acc + 'a-z'; return acc + '0-9';
case 'uppercase': return acc + 'A-Z'; case 'lowercase':
case 'symbol': return acc + CHAR_SETS.symbol.replace('-', '').replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') + '-'; return acc + 'a-z';
default: return acc; case 'uppercase':
return acc + 'A-Z';
case 'symbol':
return acc + CHAR_SETS.symbol.replace('-', '').replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') + '-';
default:
return acc;
} }
}, '') }, ''),
)].join(''); ),
].join('');
return `[${pattern}]{${options.length || 6}}`; return `[${pattern}]{${options.length || 6}}`;
} },
}); });
interface PatternConfig { interface PatternConfig {