mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-08 15:09:27 +08:00
feat: update variable handling to use joined segments and enhance helper visibility
This commit is contained in:
parent
0a687d0791
commit
ddb1340699
@ -11,8 +11,8 @@ import { Popover } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { HelperConfiguator } from './HelperConfiguator';
|
||||
|
||||
const WithPropOver = ({ children, index }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const WithPropOver = ({ children, index, defaultOpen }) => {
|
||||
const [open, setOpen] = useState(defaultOpen);
|
||||
|
||||
const handleOpenChange = (newOpen: boolean) => {
|
||||
setOpen(newOpen);
|
||||
@ -29,12 +29,14 @@ const WithPropOver = ({ children, index }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export function Helper({ index, label }: { index: number; label: string }) {
|
||||
export function Helper({ index, label, defaultOpen }: { index: number; label: string; defaultOpen: boolean }) {
|
||||
const Label = <div style={{ color: '#52c41a', display: 'inline-block', cursor: 'pointer' }}>{label}</div>;
|
||||
return (
|
||||
<>
|
||||
<span style={{ color: '#bfbfbf', margin: '0 5px' }}>|</span>
|
||||
<WithPropOver index={index}>{Label}</WithPropOver>
|
||||
<WithPropOver index={index} defaultOpen={defaultOpen}>
|
||||
{Label}
|
||||
</WithPropOver>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -11,15 +11,22 @@ import { observer } from '@formily/reactive-react';
|
||||
import React from 'react';
|
||||
import { useVariable } from '../VariableProvider';
|
||||
import { Helper } from './Helper';
|
||||
import { useHelperObservables } from './hooks/useHelperObservables';
|
||||
|
||||
const _HelperList = () => {
|
||||
const { helperObservables } = useVariable();
|
||||
const { helperObservables, openLastHelper } = useVariable();
|
||||
const { helpersObs, rawHelpersObs } = helperObservables;
|
||||
console.log(rawHelpersObs.value);
|
||||
|
||||
return (
|
||||
<>
|
||||
{helpersObs.value.map((helper, index) => {
|
||||
return <Helper key={index} index={index} label={helper.config.title} />;
|
||||
return (
|
||||
<Helper
|
||||
key={index}
|
||||
index={index}
|
||||
defaultOpen={helpersObs.value.length === index + 1 ? openLastHelper : false}
|
||||
label={helper.config.title}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
@ -57,7 +57,7 @@ export const createHelperObservables = () => {
|
||||
const { fullVariable, helpers, variableSegments } = extractTemplateElements(
|
||||
typeof template === 'string' ? template : '',
|
||||
);
|
||||
variableNameObs.value = fullVariable;
|
||||
variableNameObs.value = variableSegments.join('.');
|
||||
variableSegmentsObs.value = variableSegments;
|
||||
const computedHelpers = helpers.map((helper: any) => {
|
||||
const config = allHelpersConfigObs.value.find((f) => f.name === helper.name);
|
||||
|
@ -231,6 +231,7 @@ function _Input(props: VariableInputProps) {
|
||||
const form = useForm();
|
||||
const [options, setOptions] = React.useState<DefaultOptionType[]>([]);
|
||||
const [variableType, setVariableType] = React.useState<string>();
|
||||
const [showLastHelper, setShowLastHelper] = React.useState<boolean>(false);
|
||||
const [variableText, setVariableText] = React.useState([]);
|
||||
const [isFieldValue, setIsFieldValue] = React.useState(
|
||||
hideVariableButton || (children && value != null ? true : false),
|
||||
@ -373,7 +374,10 @@ function _Input(props: VariableInputProps) {
|
||||
if (next[1] !== type) {
|
||||
// setPrevType(next[1]);
|
||||
const newVariable = ConstantTypes[next[1]]?.default?.() ?? null;
|
||||
onChange(composeTemplate({ fullVariable: newVariable, helpers }), optionPath);
|
||||
onChange(
|
||||
composeTemplate({ fullVariable: newVariable, helpers: optionPath[optionPath.length - 1]?.helpers ?? [] }),
|
||||
optionPath,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (variable) {
|
||||
@ -382,9 +386,12 @@ function _Input(props: VariableInputProps) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
onChange(`{{${next.join('.')}}}`, optionPath);
|
||||
const variableName = next.join('.');
|
||||
const option = optionPath[optionPath.length - 1];
|
||||
onChange(composeTemplate({ fullVariable: variableName, helpers: option?.helpers ?? [] }), optionPath);
|
||||
if (Array.isArray(optionPath) && optionPath.length > 0) {
|
||||
setVariableType(optionPath[optionPath.length - 1]?.type ?? null);
|
||||
setVariableType(option.type ?? null);
|
||||
setShowLastHelper(option.showLastHelper ?? false);
|
||||
}
|
||||
},
|
||||
[type, variable, onChange],
|
||||
@ -492,6 +499,8 @@ function _Input(props: VariableInputProps) {
|
||||
<VariableProvider
|
||||
variableName={fullVariable}
|
||||
variableType={variableType}
|
||||
openLastHelper={showLastHelper}
|
||||
helperObservables={helperObservables}
|
||||
onVariableTemplateChange={onChange}
|
||||
>
|
||||
<HelperList />
|
||||
|
@ -13,8 +13,7 @@ import { composeTemplate, extractTemplateElements, Helper } from '@nocobase/json
|
||||
import { get, isArray } from 'lodash';
|
||||
import minimatch from 'minimatch';
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { useLocalVariables, useVariables } from '../../../variables';
|
||||
import { useVariablesContext } from '../../../variables/context';
|
||||
import { useLocalVariables, useVariableEvaluateContext, useVariables } from '../../../variables';
|
||||
import { dateVarsMap } from '../../../variables/date';
|
||||
import { useHelperObservables } from './Helpers/hooks/useHelperObservables';
|
||||
interface VariableContextValue {
|
||||
@ -23,13 +22,15 @@ interface VariableContextValue {
|
||||
variableType: string;
|
||||
valueType: string;
|
||||
variableName: string;
|
||||
openLastHelper?: boolean;
|
||||
}
|
||||
|
||||
interface VariableProviderProps {
|
||||
variableName: string;
|
||||
variableType: string | null;
|
||||
openLastHelper?: boolean;
|
||||
children: React.ReactNode;
|
||||
helperObservables?: ReturnType<typeof useHelperObservables>;
|
||||
helperObservables: ReturnType<typeof useHelperObservables>;
|
||||
onVariableTemplateChange?: (val) => void;
|
||||
}
|
||||
|
||||
@ -106,6 +107,7 @@ const VariableContext = createContext<VariableContextValue>({
|
||||
value: null,
|
||||
variableType: null,
|
||||
valueType: '',
|
||||
openLastHelper: false,
|
||||
});
|
||||
|
||||
export function useCurrentVariable(): VariableContextValue {
|
||||
@ -120,13 +122,15 @@ const _VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
variableName,
|
||||
children,
|
||||
variableType,
|
||||
openLastHelper,
|
||||
helperObservables,
|
||||
onVariableTemplateChange,
|
||||
}) => {
|
||||
const [value, setValue] = useState(null);
|
||||
const variables = useVariables();
|
||||
const localVariables = useLocalVariables();
|
||||
const helperObservables = useHelperObservables();
|
||||
isArray(localVariables) ? localVariables : [localVariables];
|
||||
const { getValue } = useVariableEvaluateContext();
|
||||
useEffect(() => {
|
||||
const dispose = reaction(
|
||||
() => {
|
||||
@ -137,14 +141,11 @@ const _VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
},
|
||||
);
|
||||
return dispose;
|
||||
}, [variableName, onVariableTemplateChange]);
|
||||
}, [variableName, onVariableTemplateChange, helperObservables.helpersObs.value]);
|
||||
useEffect(() => {
|
||||
async function fetchValue() {
|
||||
try {
|
||||
const vars = {
|
||||
$nDate: dateVarsMap,
|
||||
};
|
||||
const val = get(vars, variableName);
|
||||
const val = await getValue(variableName);
|
||||
if (val) {
|
||||
setValue(val);
|
||||
} else {
|
||||
@ -156,7 +157,7 @@ const _VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
}
|
||||
}
|
||||
fetchValue();
|
||||
}, [localVariables, variableName, variables]);
|
||||
}, [localVariables, variableName, variables, getValue]);
|
||||
|
||||
const valueType =
|
||||
helperObservables.helpersObs.value.length > 0
|
||||
@ -164,7 +165,9 @@ const _VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
: variableType;
|
||||
|
||||
return (
|
||||
<VariableContext.Provider value={{ variableName, value, valueType, helperObservables, variableType }}>
|
||||
<VariableContext.Provider
|
||||
value={{ variableName, value, valueType, helperObservables, variableType, openLastHelper }}
|
||||
>
|
||||
{children}
|
||||
</VariableContext.Provider>
|
||||
);
|
||||
|
@ -1,13 +1,98 @@
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer, useField, useForm } from '@formily/react';
|
||||
import { AntdSchemaComponentProvider, Plugin, SchemaComponent } from '@nocobase/client';
|
||||
import { AntdSchemaComponentProvider, Plugin, SchemaComponent, VariableEvaluateProvider } from '@nocobase/client';
|
||||
import { mockApp } from '@nocobase/client/demo-utils';
|
||||
import { createJSONTemplateParser } from '@nocobase/json-template-parser';
|
||||
import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client';
|
||||
import React from 'react';
|
||||
import { dayjs } from '@nocobase/utils/client';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
const parser = createJSONTemplateParser();
|
||||
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 30, 60, 90];
|
||||
const scope = [
|
||||
{ label: 'v1', value: 'v1' },
|
||||
{ label: 'Date', value: '$nDate', children: [{ label: 'Now', value: 'now', type: 'date' }] },
|
||||
{
|
||||
label: 'Date',
|
||||
value: '$nDate',
|
||||
type: 'date',
|
||||
children: [
|
||||
{ label: 'Now', value: 'now', type: 'date' },
|
||||
{
|
||||
label: 'before',
|
||||
value: 'before',
|
||||
type: 'date',
|
||||
children: numbers.map((number) => ({
|
||||
label: `${number}`,
|
||||
value: `${number}`,
|
||||
children: [
|
||||
{
|
||||
label: 'days',
|
||||
value: 'day',
|
||||
type: 'date',
|
||||
helpers: [
|
||||
{
|
||||
name: 'date_format',
|
||||
args: ['YYYY-MM-DD'],
|
||||
},
|
||||
],
|
||||
showLastHelper: true,
|
||||
},
|
||||
{
|
||||
label: 'weeks',
|
||||
value: 'week',
|
||||
type: 'date',
|
||||
helpers: [
|
||||
{
|
||||
name: 'date_format',
|
||||
args: ['YYYY-MM-DD'],
|
||||
},
|
||||
],
|
||||
showLastHelper: true,
|
||||
},
|
||||
{
|
||||
label: 'months',
|
||||
value: 'month',
|
||||
type: 'date',
|
||||
helpers: [
|
||||
{
|
||||
name: 'date_format',
|
||||
args: ['YYYY-MM-DD'],
|
||||
},
|
||||
],
|
||||
showLastHelper: true,
|
||||
},
|
||||
{
|
||||
label: 'years',
|
||||
value: 'year',
|
||||
type: 'date',
|
||||
helpers: [
|
||||
{
|
||||
name: 'date_format',
|
||||
args: ['YYYY-MM-DD'],
|
||||
},
|
||||
],
|
||||
showLastHelper: true,
|
||||
},
|
||||
],
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'after',
|
||||
value: 'after',
|
||||
type: 'date',
|
||||
children: numbers.map((number) => ({
|
||||
label: `${number}`,
|
||||
value: `${number}`,
|
||||
children: [
|
||||
{ label: 'days', value: 'day', type: 'date' },
|
||||
{ label: 'weeks', value: 'week', type: 'date' },
|
||||
{ label: 'months', value: 'month', type: 'date' },
|
||||
{ label: 'years', value: 'year', type: 'date' },
|
||||
],
|
||||
})),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const useFormBlockProps = () => {
|
||||
@ -34,22 +119,64 @@ const schema = {
|
||||
},
|
||||
output: {
|
||||
type: 'void',
|
||||
title: `输出`,
|
||||
title: `模板`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'OutPut',
|
||||
},
|
||||
result: {
|
||||
type: 'void',
|
||||
title: `值`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Result',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const OutPut = observer(() => {
|
||||
const form = useForm();
|
||||
return <div>Current input value: {form.values.input}</div>;
|
||||
return <div>{form.values.input}</div>;
|
||||
});
|
||||
|
||||
const Result = observer(() => {
|
||||
const form = useForm();
|
||||
const [value, setValue] = useState<string>('');
|
||||
useEffect(() => {
|
||||
if (!form.values.input) {
|
||||
return;
|
||||
}
|
||||
parser
|
||||
.render(form.values.input, { $nDate: dateScopeFn }, {})
|
||||
.then((result) => {
|
||||
setValue(result);
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error;
|
||||
});
|
||||
}, [form.values.input]);
|
||||
return <div>{value.toString()}</div>;
|
||||
});
|
||||
|
||||
const dateScopeFn = ({ fields, data, context }) => {
|
||||
return {
|
||||
getValue: ({ field, keys }) => {
|
||||
const path = field.split('.');
|
||||
if (path[0] === 'now') {
|
||||
return dayjs();
|
||||
} else if (path[0] === 'before') {
|
||||
return dayjs().subtract(parseInt(path[1]), path[2]);
|
||||
} else if (path[0] === 'after') {
|
||||
return dayjs().add(parseInt(path[1]), path[2]);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
};
|
||||
const Demo = () => {
|
||||
return (
|
||||
<AntdSchemaComponentProvider>
|
||||
<SchemaComponent schema={schema} scope={{ useFormBlockProps }} components={{ OutPut }} />
|
||||
<VariableEvaluateProvider data={{ $nDate: dateScopeFn }} context={{}}>
|
||||
<SchemaComponent schema={schema} scope={{ useFormBlockProps }} components={{ OutPut, Result }} />
|
||||
</VariableEvaluateProvider>
|
||||
</AntdSchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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 { get } from 'lodash';
|
||||
import React, { createContext, ReactNode, useContext } from 'react';
|
||||
|
||||
interface EvaluateContextProps {
|
||||
data: Record<string, any>;
|
||||
context: Record<string, any>;
|
||||
getValue: (field: string) => Promise<any>;
|
||||
}
|
||||
|
||||
const EvaluateContext = createContext<EvaluateContextProps>({ data: {}, context: {}, getValue: async () => null });
|
||||
|
||||
export const VariableEvaluateProvider: React.FC<{
|
||||
children: ReactNode;
|
||||
data: Record<string, any>;
|
||||
context: Record<string, any>;
|
||||
}> = ({ children, data, context }) => {
|
||||
const getValueFromData = async (field: string): Promise<any> => {
|
||||
return getValue({ field, data, context });
|
||||
};
|
||||
return (
|
||||
<EvaluateContext.Provider value={{ data, context, getValue: getValueFromData }}>
|
||||
{children}
|
||||
</EvaluateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useVariableEvaluateContext = (): EvaluateContextProps => {
|
||||
const context = useContext(EvaluateContext);
|
||||
if (!context) {
|
||||
throw new Error('useEvaluate must be used within an EvaluateProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
const getValue = async (params: {
|
||||
field: string;
|
||||
data: Record<string, any>;
|
||||
context?: Record<string, any>;
|
||||
}): Promise<any> => {
|
||||
const { field, data, context } = params;
|
||||
const path = field.split('.');
|
||||
const dataKey = path.slice(1).join('.');
|
||||
// Handle scope functions (starting with $)
|
||||
if (path[0].startsWith('$')) {
|
||||
const scopeKey = path[0];
|
||||
const scopeFn = data[scopeKey];
|
||||
if (typeof scopeFn === 'function') {
|
||||
const scopeResult = await scopeFn({ fields: [dataKey], data, context });
|
||||
if (scopeResult?.getValue) {
|
||||
return scopeResult.getValue({ field: dataKey, keys: [] });
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = get(data, dataKey);
|
||||
|
||||
// Handle function values
|
||||
if (typeof value === 'function') {
|
||||
return value();
|
||||
}
|
||||
return value;
|
||||
};
|
@ -7,13 +7,15 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
export { default as VariablesProvider, VariablesContext } from './VariablesProvider';
|
||||
export { default as useBuiltinVariables } from './hooks/useBuiltinVariables';
|
||||
export { default as useContextVariable } from './hooks/useContextVariable';
|
||||
export { default as useLocalVariables } from './hooks/useLocalVariables';
|
||||
export { default as useVariables } from './hooks/useVariables';
|
||||
export * from './utils/isVariable';
|
||||
export * from './utils/transformVariableValue';
|
||||
export { VariablesContext, default as VariablesProvider } from './VariablesProvider';
|
||||
|
||||
export * from './constants';
|
||||
export type { VariablesContextType } from './types';
|
||||
|
||||
export * from './context/EvaluateContext';
|
||||
|
@ -159,7 +159,7 @@ export class JSONTemplateParser {
|
||||
fieldSet.add(field);
|
||||
}
|
||||
return {
|
||||
variableName: fullVariables[0],
|
||||
variableName: variableSegments.join('.'),
|
||||
variableSegments,
|
||||
tokenKind: rawTemplate.token.kind,
|
||||
tokenBegin: rawTemplate.token.begin,
|
||||
|
@ -30,8 +30,8 @@ export function extractTemplateElements(template: string): {
|
||||
} {
|
||||
const escapedTemplate = escape(template ?? '');
|
||||
try {
|
||||
const fullVariable = engine.fullVariablesSync(escapedTemplate)[0] ?? '';
|
||||
const variableSegments = engine.variableSegmentsSync(escapedTemplate)[0] ?? [];
|
||||
const fullVariable = variableSegments.join('.');
|
||||
const parsedTemplate = engine.parse(escapedTemplate)[0] ?? {};
|
||||
const helpers =
|
||||
//@ts-ignore
|
||||
|
Loading…
x
Reference in New Issue
Block a user