mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-09 15:39:24 +08:00
fix: some problems
This commit is contained in:
parent
02097cd96d
commit
c8ecdb7c2c
@ -27,8 +27,8 @@
|
||||
"@formily/reactive-react": "^2.2.27",
|
||||
"@formily/shared": "^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/json-template-parser": "1.6.0-alpha.28",
|
||||
"@nocobase/sdk": "1.7.0-alpha.3",
|
||||
"@nocobase/utils": "1.7.0-alpha.3",
|
||||
"ahooks": "^3.7.2",
|
||||
@ -67,7 +67,8 @@
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-to-print": "^2.14.7",
|
||||
"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": {
|
||||
"react": ">=18.0.0",
|
||||
|
@ -244,17 +244,17 @@ function _Input(props: VariableInputProps) {
|
||||
[value],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const dispose = reaction(
|
||||
() => {
|
||||
return composeTemplate({ fullVariable, helpers: helperObservables.helpersObs.value });
|
||||
},
|
||||
(newVal) => {
|
||||
onChange(newVal);
|
||||
},
|
||||
);
|
||||
return dispose;
|
||||
}, [fullVariable, onChange]);
|
||||
// useEffect(() => {
|
||||
// const dispose = reaction(
|
||||
// () => {
|
||||
// return composeTemplate({ fullVariable, helpers: helperObservables.helpersObs.value });
|
||||
// },
|
||||
// (newVal) => {
|
||||
// onChange(newVal);
|
||||
// },
|
||||
// );
|
||||
// return dispose;
|
||||
// }, [fullVariable, onChange]);
|
||||
|
||||
const parsed = useMemo(() => parseValue(variableSegments, parseOptions), [parseOptions, variableSegments]);
|
||||
const isConstant = typeof parsed === 'string';
|
||||
@ -485,7 +485,11 @@ function _Input(props: VariableInputProps) {
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
<VariableProvider variableName={fullVariable} variableHelperMapping={variableHelperMapping}>
|
||||
<VariableProvider
|
||||
variableName={fullVariable}
|
||||
variableHelperMapping={variableHelperMapping}
|
||||
onVariableTemplateChange={onChange}
|
||||
>
|
||||
<HelperList />
|
||||
{variableText.length > 0 && <HelperAddition />}
|
||||
</VariableProvider>
|
||||
|
@ -7,7 +7,8 @@
|
||||
* 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 minimatch from 'minimatch';
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
@ -25,6 +26,7 @@ interface VariableProviderProps {
|
||||
children: React.ReactNode;
|
||||
variableHelperMapping?: VariableHelperMapping;
|
||||
helperObservables?: ReturnType<typeof useHelperObservables>;
|
||||
onVariableTemplateChange?: (val) => void;
|
||||
}
|
||||
|
||||
export interface VariableHelperRule {
|
||||
@ -129,12 +131,24 @@ export const VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
variableName,
|
||||
children,
|
||||
variableHelperMapping,
|
||||
onVariableTemplateChange,
|
||||
}) => {
|
||||
const [value, setValue] = useState(null);
|
||||
const variables = useVariables();
|
||||
const localVariables = useLocalVariables();
|
||||
const helperObservables = useHelperObservables();
|
||||
isArray(localVariables) ? localVariables : [localVariables];
|
||||
useEffect(() => {
|
||||
const dispose = reaction(
|
||||
() => {
|
||||
return composeTemplate({ fullVariable: variableName, helpers: helperObservables.helpersObs.value });
|
||||
},
|
||||
(newVal) => {
|
||||
onVariableTemplateChange(newVal);
|
||||
},
|
||||
);
|
||||
return dispose;
|
||||
}, [variableName, onVariableTemplateChange]);
|
||||
useEffect(() => {
|
||||
async function fetchValue() {
|
||||
try {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer, useField, useForm } from '@formily/react';
|
||||
import { AntdSchemaComponentProvider, Plugin, SchemaComponent } from '@nocobase/client';
|
||||
import { mockApp } from '@nocobase/client/demo-utils';
|
||||
import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client';
|
||||
@ -8,8 +10,18 @@ const scope = [
|
||||
{ label: 'Date', value: '$date', children: [{ label: 'Now', value: 'now' }] },
|
||||
];
|
||||
|
||||
const useFormBlockProps = () => {
|
||||
return {
|
||||
form: createForm({
|
||||
initialValues: {},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
'x-component': 'FormV2',
|
||||
'x-use-component-props': 'useFormBlockProps',
|
||||
properties: {
|
||||
input: {
|
||||
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 = () => {
|
||||
return (
|
||||
<AntdSchemaComponentProvider>
|
||||
<SchemaComponent schema={schema} />
|
||||
<SchemaComponent schema={schema} scope={{ useFormBlockProps }} components={{ OutPut }} />
|
||||
</AntdSchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
|
@ -45,6 +45,8 @@ const variableHelperMapping = {
|
||||
```
|
||||
<code src="./demos/demo1.tsx"></code>
|
||||
|
||||
### 不同的变量有不同的上下文
|
||||
同一个变量在不同的运行时环境它的值也不同,所以有必要为变量提供上下文环境。
|
||||
## `Variable.TextArea`
|
||||
|
||||
<code src="./demos/demo2.tsx"></code>
|
||||
|
@ -26,6 +26,7 @@ import { getPath } from './utils/getPath';
|
||||
import { clearRequested, getRequested, hasRequested, stashRequested } from './utils/hasRequested';
|
||||
import { isVariable } from './utils/isVariable';
|
||||
import { uniq } from './utils/uniq';
|
||||
import { processDateVariableContext } from './utils/dateVariableContext';
|
||||
|
||||
export const VariablesContext = createContext<VariablesContextType>(null);
|
||||
VariablesContext.displayName = 'VariablesContext';
|
||||
@ -257,44 +258,51 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
|
||||
);
|
||||
|
||||
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 (
|
||||
str: string,
|
||||
localVariables?: VariableOption | VariableOption[],
|
||||
localVariable?: VariableOption | VariableOption[],
|
||||
options?: {
|
||||
/** Related fields that need to be included in the first request */
|
||||
appends?: string[];
|
||||
/** Do not request when the association field is empty */
|
||||
doNotRequest?: boolean;
|
||||
/**
|
||||
* The operator related to the current field, provided when parsing the default value of the field
|
||||
*/
|
||||
fieldOperator?: string | void;
|
||||
},
|
||||
) => {
|
||||
const { fullVariable, helpers } = extractTemplateElements(str);
|
||||
if (!fullVariable) {
|
||||
return str;
|
||||
const list = variablePath.split('.');
|
||||
const variableName = list[0];
|
||||
const variableOption = variablesStore[variableName];
|
||||
|
||||
if (!variableOption) {
|
||||
return {
|
||||
value: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (localVariables) {
|
||||
localVariables = _.isArray(localVariables) ? localVariables : [localVariables];
|
||||
}
|
||||
const _value = await getResult(variablePath, localVariable, options);
|
||||
|
||||
const path = getPath(str);
|
||||
const result = await getResult(path, localVariables as VariableOption[], options);
|
||||
if (Array.isArray(helpers) && helpers.length > 0) {
|
||||
result.value = helpers.reduce((acc, helper) => helper.handler(...[acc, ...helper.args]), result.value);
|
||||
// 处理变量上下文
|
||||
if (variableOption.variableContext) {
|
||||
const { type, config } = variableOption.variableContext;
|
||||
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 {
|
||||
...result,
|
||||
value: uniq(filterEmptyValues(result.value)),
|
||||
value: _value === undefined ? variableOption.defaultValue : _value,
|
||||
collectionName: variableOption.collectionName,
|
||||
dataSource: variableOption.dataSource,
|
||||
};
|
||||
},
|
||||
[getResult],
|
||||
|
56
packages/core/client/src/variables/context/index.tsx
Normal file
56
packages/core/client/src/variables/context/index.tsx
Normal 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;
|
||||
};
|
41
packages/core/client/src/variables/date/index.tsx
Normal file
41
packages/core/client/src/variables/date/index.tsx
Normal 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(),
|
||||
},
|
||||
];
|
@ -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,
|
||||
};
|
||||
}
|
@ -129,4 +129,21 @@ export interface VariableOption {
|
||||
* 如果想让数据范围中的 filter 条件被清除掉,可以设置 defaultValue 为 undefined。
|
||||
*/
|
||||
defaultValue?: any;
|
||||
/**
|
||||
* 变量的上下文配置,用于存储变量的额外配置信息
|
||||
* 例如:日期变量的时区配置
|
||||
*/
|
||||
variableContext?: {
|
||||
/** 变量类型 */
|
||||
type: 'date' | 'string' | 'number' | 'boolean';
|
||||
/** 变量特定的配置 */
|
||||
config: {
|
||||
/** 时区配置,仅对日期类型有效 */
|
||||
timezone?: string;
|
||||
/** 日期格式配置,仅对日期类型有效 */
|
||||
dateFormat?: string;
|
||||
/** 其他类型特定的配置 */
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user