fix: some problems

This commit is contained in:
sheldon66 2025-03-23 18:24:22 +08:00
parent 02097cd96d
commit c8ecdb7c2c
11 changed files with 322 additions and 40 deletions

View File

@ -27,8 +27,8 @@
"@formily/reactive-react": "^2.2.27", "@formily/reactive-react": "^2.2.27",
"@formily/shared": "^2.2.27", "@formily/shared": "^2.2.27",
"@formily/validator": "^2.2.27", "@formily/validator": "^2.2.27",
"@nocobase/json-template-parser": "1.6.0-alpha.28",
"@nocobase/evaluators": "1.7.0-alpha.3", "@nocobase/evaluators": "1.7.0-alpha.3",
"@nocobase/json-template-parser": "1.6.0-alpha.28",
"@nocobase/sdk": "1.7.0-alpha.3", "@nocobase/sdk": "1.7.0-alpha.3",
"@nocobase/utils": "1.7.0-alpha.3", "@nocobase/utils": "1.7.0-alpha.3",
"ahooks": "^3.7.2", "ahooks": "^3.7.2",
@ -67,7 +67,8 @@
"react-router-dom": "^6.11.2", "react-router-dom": "^6.11.2",
"react-to-print": "^2.14.7", "react-to-print": "^2.14.7",
"sanitize-html": "2.13.0", "sanitize-html": "2.13.0",
"use-deep-compare-effect": "^1.8.1" "use-deep-compare-effect": "^1.8.1",
"use-immer": "^0.11.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": ">=18.0.0", "react": ">=18.0.0",

View File

@ -244,17 +244,17 @@ function _Input(props: VariableInputProps) {
[value], [value],
); );
useEffect(() => { // useEffect(() => {
const dispose = reaction( // const dispose = reaction(
() => { // () => {
return composeTemplate({ fullVariable, helpers: helperObservables.helpersObs.value }); // return composeTemplate({ fullVariable, helpers: helperObservables.helpersObs.value });
}, // },
(newVal) => { // (newVal) => {
onChange(newVal); // onChange(newVal);
}, // },
); // );
return dispose; // return dispose;
}, [fullVariable, onChange]); // }, [fullVariable, onChange]);
const parsed = useMemo(() => parseValue(variableSegments, parseOptions), [parseOptions, variableSegments]); const parsed = useMemo(() => parseValue(variableSegments, parseOptions), [parseOptions, variableSegments]);
const isConstant = typeof parsed === 'string'; const isConstant = typeof parsed === 'string';
@ -485,7 +485,11 @@ function _Input(props: VariableInputProps) {
</React.Fragment> </React.Fragment>
); );
})} })}
<VariableProvider variableName={fullVariable} variableHelperMapping={variableHelperMapping}> <VariableProvider
variableName={fullVariable}
variableHelperMapping={variableHelperMapping}
onVariableTemplateChange={onChange}
>
<HelperList /> <HelperList />
{variableText.length > 0 && <HelperAddition />} {variableText.length > 0 && <HelperAddition />}
</VariableProvider> </VariableProvider>

View File

@ -7,7 +7,8 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * For more information, please refer to: https://www.nocobase.com/agreement.
*/ */
import { Helper } from '@nocobase/json-template-parser'; import { reaction } from '@formily/reactive';
import { composeTemplate, extractTemplateElements, Helper } from '@nocobase/json-template-parser';
import { isArray } from 'lodash'; import { isArray } from 'lodash';
import minimatch from 'minimatch'; import minimatch from 'minimatch';
import React, { createContext, useContext, useEffect, useState } from 'react'; import React, { createContext, useContext, useEffect, useState } from 'react';
@ -25,6 +26,7 @@ interface VariableProviderProps {
children: React.ReactNode; children: React.ReactNode;
variableHelperMapping?: VariableHelperMapping; variableHelperMapping?: VariableHelperMapping;
helperObservables?: ReturnType<typeof useHelperObservables>; helperObservables?: ReturnType<typeof useHelperObservables>;
onVariableTemplateChange?: (val) => void;
} }
export interface VariableHelperRule { export interface VariableHelperRule {
@ -129,12 +131,24 @@ export const VariableProvider: React.FC<VariableProviderProps> = ({
variableName, variableName,
children, children,
variableHelperMapping, variableHelperMapping,
onVariableTemplateChange,
}) => { }) => {
const [value, setValue] = useState(null); const [value, setValue] = useState(null);
const variables = useVariables(); const variables = useVariables();
const localVariables = useLocalVariables(); const localVariables = useLocalVariables();
const helperObservables = useHelperObservables(); const helperObservables = useHelperObservables();
isArray(localVariables) ? localVariables : [localVariables]; isArray(localVariables) ? localVariables : [localVariables];
useEffect(() => {
const dispose = reaction(
() => {
return composeTemplate({ fullVariable: variableName, helpers: helperObservables.helpersObs.value });
},
(newVal) => {
onVariableTemplateChange(newVal);
},
);
return dispose;
}, [variableName, onVariableTemplateChange]);
useEffect(() => { useEffect(() => {
async function fetchValue() { async function fetchValue() {
try { try {

View File

@ -1,3 +1,5 @@
import { createForm } from '@formily/core';
import { observer, useField, useForm } from '@formily/react';
import { AntdSchemaComponentProvider, Plugin, SchemaComponent } from '@nocobase/client'; import { AntdSchemaComponentProvider, Plugin, SchemaComponent } from '@nocobase/client';
import { mockApp } from '@nocobase/client/demo-utils'; import { mockApp } from '@nocobase/client/demo-utils';
import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client'; import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client';
@ -8,8 +10,18 @@ const scope = [
{ label: 'Date', value: '$date', children: [{ label: 'Now', value: 'now' }] }, { label: 'Date', value: '$date', children: [{ label: 'Now', value: 'now' }] },
]; ];
const useFormBlockProps = () => {
return {
form: createForm({
initialValues: {},
}),
};
};
const schema = { const schema = {
type: 'object', type: 'object',
'x-component': 'FormV2',
'x-use-component-props': 'useFormBlockProps',
properties: { properties: {
input: { input: {
type: 'string', type: 'string',
@ -28,13 +40,24 @@ const schema = {
}, },
}, },
}, },
output: {
type: 'void',
title: `输出`,
'x-decorator': 'FormItem',
'x-component': 'OutPut',
},
}, },
}; };
const OutPut = observer(() => {
const form = useForm();
return <div>Current input value: {form.values.input}</div>;
});
const Demo = () => { const Demo = () => {
return ( return (
<AntdSchemaComponentProvider> <AntdSchemaComponentProvider>
<SchemaComponent schema={schema} /> <SchemaComponent schema={schema} scope={{ useFormBlockProps }} components={{ OutPut }} />
</AntdSchemaComponentProvider> </AntdSchemaComponentProvider>
); );
}; };

View File

@ -45,6 +45,8 @@ const variableHelperMapping = {
``` ```
<code src="./demos/demo1.tsx"></code> <code src="./demos/demo1.tsx"></code>
### 不同的变量有不同的上下文
同一个变量在不同的运行时环境它的值也不同,所以有必要为变量提供上下文环境。
## `Variable.TextArea` ## `Variable.TextArea`
<code src="./demos/demo2.tsx"></code> <code src="./demos/demo2.tsx"></code>

View File

@ -26,6 +26,7 @@ import { getPath } from './utils/getPath';
import { clearRequested, getRequested, hasRequested, stashRequested } from './utils/hasRequested'; import { clearRequested, getRequested, hasRequested, stashRequested } from './utils/hasRequested';
import { isVariable } from './utils/isVariable'; import { isVariable } from './utils/isVariable';
import { uniq } from './utils/uniq'; import { uniq } from './utils/uniq';
import { processDateVariableContext } from './utils/dateVariableContext';
export const VariablesContext = createContext<VariablesContextType>(null); export const VariablesContext = createContext<VariablesContextType>(null);
VariablesContext.displayName = 'VariablesContext'; VariablesContext.displayName = 'VariablesContext';
@ -257,44 +258,51 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
); );
const parseVariable = useCallback( const parseVariable = useCallback(
/**
* Parse the variable string to the actual value
* @param str Variable string
* @param localVariables Local variables, will be cleared after parsing
* @returns
*/
async ( async (
str: string, str: string,
localVariables?: VariableOption | VariableOption[], localVariable?: VariableOption | VariableOption[],
options?: { options?: {
/** Related fields that need to be included in the first request */
appends?: string[]; appends?: string[];
/** Do not request when the association field is empty */
doNotRequest?: boolean; doNotRequest?: boolean;
/**
* The operator related to the current field, provided when parsing the default value of the field
*/
fieldOperator?: string | void; fieldOperator?: string | void;
}, },
) => { ) => {
const { fullVariable, helpers } = extractTemplateElements(str); const list = variablePath.split('.');
if (!fullVariable) { const variableName = list[0];
return str; const variableOption = variablesStore[variableName];
if (!variableOption) {
return {
value: undefined,
};
} }
if (localVariables) { const _value = await getResult(variablePath, localVariable, options);
localVariables = _.isArray(localVariables) ? localVariables : [localVariables];
}
const path = getPath(str); // 处理变量上下文
const result = await getResult(path, localVariables as VariableOption[], options); if (variableOption.variableContext) {
if (Array.isArray(helpers) && helpers.length > 0) { const { type, config } = variableOption.variableContext;
result.value = helpers.reduce((acc, helper) => helper.handler(...[acc, ...helper.args]), result.value); switch (type) {
case 'date':
return {
value: processDateVariableContext(_value, config),
collectionName: variableOption.collectionName,
dataSource: variableOption.dataSource,
};
// 可以添加其他类型的处理
default:
return {
value: _value,
collectionName: variableOption.collectionName,
dataSource: variableOption.dataSource,
};
}
} }
return { return {
...result, value: _value === undefined ? variableOption.defaultValue : _value,
value: uniq(filterEmptyValues(result.value)), collectionName: variableOption.collectionName,
dataSource: variableOption.dataSource,
}; };
}, },
[getResult], [getResult],

View File

@ -0,0 +1,56 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { DateVariableContext } from '../date';
import { useImmer } from 'use-immer';
import { Updater } from 'use-immer';
import React, { createContext, useContext } from 'react';
import merge from 'lodash/merge';
type VariablesEvalContextType = {
$dateV2: DateVariableContext;
};
type VariablesContextType = {
vairableEvalContext: VariablesEvalContextType;
setVariableEvalContext: Updater<VariablesEvalContextType>;
};
const VariablesContext = createContext<VariablesContextType | null>(null);
export const VariablesContextProvider = ({
children,
variablesCtx: variablesCtxProp,
}: {
children: React.ReactNode;
variablesCtx: Partial<VariablesEvalContextType>;
}) => {
const clientTZ = Intl.DateTimeFormat().resolvedOptions().timeZone;
const defaultVariablesCtx: VariablesEvalContextType = {
$dateV2: {
timezone: clientTZ,
rangeConverter: 'start',
},
};
const mergedVariablesCtx = merge(defaultVariablesCtx, variablesCtxProp);
const [vairableEvalContext, setVariableEvalContext] = useImmer<VariablesEvalContextType>(mergedVariablesCtx);
return (
<VariablesContext.Provider value={{ vairableEvalContext, setVariableEvalContext }}>
{children}
</VariablesContext.Provider>
);
};
export const useVariablesContext = () => {
const variablesCtx = useContext(VariablesContext);
if (!variablesCtx) {
throw new Error('useVariablesContext must be used within a VariablesContextProvider');
}
return variablesCtx;
};

View File

@ -0,0 +1,41 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
// date context
export type DateVariableContext = {
timezone: string;
rangeConverter: 'start' | 'end' | 'range';
};
const dateVars = [
{
key: 'now',
label: 'Now',
type: 'date',
exampleValue: new Date(),
},
{
key: 'today',
label: 'Today',
type: 'date',
exampleValue: new Date(),
},
{
key: 'yesterday',
label: 'Yesterday',
type: 'date',
exampleValue: new Date(),
},
{
key: 'tomorrow',
label: 'Tomorrow',
type: 'date',
exampleValue: new Date(),
},
];

View File

@ -0,0 +1,63 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { useCallback } from 'react';
import { useVariables } from './useVariables';
import { VariableOption } from '../types';
export function useVariableContext() {
const { registerVariable, getVariable } = useVariables();
/**
*
* @param variableOption
*/
const registerVariableWithContext = useCallback(
(variableOption: VariableOption) => {
registerVariable(variableOption);
},
[registerVariable],
);
/**
*
* @param variableName
*/
const getVariableContext = useCallback(
(variableName: string) => {
const variable = getVariable(variableName);
return variable?.variableContext;
},
[getVariable],
);
/**
*
* @param variableName
* @param context
*/
const updateVariableContext = useCallback(
(variableName: string, context: VariableOption['variableContext']) => {
const variable = getVariable(variableName);
if (!variable) return;
registerVariable({
...variable,
variableContext: context,
});
},
[getVariable, registerVariable],
);
return {
registerVariableWithContext,
getVariableContext,
updateVariableContext,
};
}

View File

@ -129,4 +129,21 @@ export interface VariableOption {
* filter defaultValue undefined * filter defaultValue undefined
*/ */
defaultValue?: any; defaultValue?: any;
/**
*
*
*/
variableContext?: {
/** 变量类型 */
type: 'date' | 'string' | 'number' | 'boolean';
/** 变量特定的配置 */
config: {
/** 时区配置,仅对日期类型有效 */
timezone?: string;
/** 日期格式配置,仅对日期类型有效 */
dateFormat?: string;
/** 其他类型特定的配置 */
[key: string]: any;
};
};
} }

View File

@ -0,0 +1,53 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
dayjs.extend(utc);
dayjs.extend(timezone);
/**
*
* @param value
* @param context
*/
export function processDateVariableContext(value: any, context?: { timezone?: string; dateFormat?: string }) {
if (!value) return value;
let date = dayjs(value);
// 应用时区
if (context?.timezone) {
date = date.tz(context.timezone);
}
// 应用日期格式
if (context?.dateFormat) {
return date.format(context.dateFormat);
}
return date.toISOString();
}
/**
*
* @param timezone
* @param dateFormat
*/
export function createDateVariableContext(timezone?: string, dateFormat?: string) {
return {
type: 'date' as const,
config: {
timezone,
dateFormat,
},
};
}