mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-07 14:39:25 +08:00
feat: refactor doc
This commit is contained in:
parent
abcb81e63a
commit
483f22f56d
@ -22,6 +22,7 @@ import {
|
||||
Select,
|
||||
Space,
|
||||
Tag,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import useAntdInputStyle from 'antd/es/input/style';
|
||||
@ -35,7 +36,7 @@ import { Json } from '../input';
|
||||
import { HelperAddition, HelperList } from './Helpers';
|
||||
import { useHelperObservables } from './Helpers/hooks/useHelperObservables';
|
||||
import { useStyles } from './style';
|
||||
import { VariableHelperMapping, VariableProvider } from './VariableProvider';
|
||||
import { useVariable, VariableHelperMapping, VariableProvider } from './VariableProvider';
|
||||
import { XButton } from './XButton';
|
||||
|
||||
const { Text } = Typography;
|
||||
@ -45,6 +46,22 @@ type ParseOptions = {
|
||||
stringToDate?: boolean;
|
||||
};
|
||||
|
||||
const findScopeOption = (options: DefaultOptionType[], path: string[]): DefaultOptionType | null => {
|
||||
if (!options || !Array.isArray(options)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const option of options) {
|
||||
if (option.value === path[0]) {
|
||||
if (path.length === 1) {
|
||||
return option;
|
||||
}
|
||||
return findScopeOption(option.children, path.slice(1));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
function parseValue(value: any, options: ParseOptions = {}): string | string[] {
|
||||
if (value == null || (Array.isArray(value) && value.length === 0)) {
|
||||
return 'null';
|
||||
@ -220,6 +237,7 @@ function _Input(props: VariableInputProps) {
|
||||
} = props;
|
||||
|
||||
const scope = typeof props.scope === 'function' ? props.scope() : props.scope;
|
||||
|
||||
const { wrapSSR, hashId, componentCls, rootPrefixCls } = useStyles({ hideVariableButton });
|
||||
|
||||
const helperObservables = useHelperObservables();
|
||||
@ -230,8 +248,6 @@ function _Input(props: VariableInputProps) {
|
||||
const { t } = useTranslation();
|
||||
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),
|
||||
@ -246,17 +262,7 @@ function _Input(props: VariableInputProps) {
|
||||
[value],
|
||||
);
|
||||
|
||||
// useEffect(() => {
|
||||
// const dispose = reaction(
|
||||
// () => {
|
||||
// return composeTemplate({ fullVariable, helpers: helperObservables.helpersObs.value });
|
||||
// },
|
||||
// (newVal) => {
|
||||
// onChange(newVal);
|
||||
// },
|
||||
// );
|
||||
// return dispose;
|
||||
// }, [fullVariable, onChange]);
|
||||
const selectedScopeOption = useMemo(() => findScopeOption(scope, variableSegments), [scope, variableSegments]);
|
||||
|
||||
const parsed = useMemo(() => parseValue(variableSegments, parseOptions), [parseOptions, variableSegments]);
|
||||
const isConstant = typeof parsed === 'string';
|
||||
@ -389,10 +395,6 @@ function _Input(props: VariableInputProps) {
|
||||
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(option.type ?? null);
|
||||
setShowLastHelper(option.showLastHelper ?? false);
|
||||
}
|
||||
},
|
||||
[type, variable, onChange],
|
||||
);
|
||||
@ -488,21 +490,16 @@ function _Input(props: VariableInputProps) {
|
||||
className={cx('ant-input ant-input-outlined', { 'ant-input-disabled': disabled }, hashId)}
|
||||
>
|
||||
<Tag color="blue">
|
||||
{variableText.map((item, index) => {
|
||||
return (
|
||||
<React.Fragment key={item}>
|
||||
{index ? ' / ' : ''}
|
||||
{item}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
<VariableProvider
|
||||
variableName={fullVariable}
|
||||
variableType={variableType}
|
||||
openLastHelper={showLastHelper}
|
||||
variableExampleValue={selectedScopeOption?.example}
|
||||
variableType={selectedScopeOption?.type}
|
||||
openLastHelper={selectedScopeOption?.showLastHelper}
|
||||
helperObservables={helperObservables}
|
||||
onVariableTemplateChange={onChange}
|
||||
>
|
||||
<VariableTag variablePath={variableText} />
|
||||
|
||||
<HelperList />
|
||||
{variableText.length > 0 && <HelperAddition />}
|
||||
</VariableProvider>
|
||||
@ -561,4 +558,22 @@ function _Input(props: VariableInputProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const VariableTag = ({ variablePath }: { variablePath: string[] }) => {
|
||||
const { value } = useVariable();
|
||||
return (
|
||||
<Tooltip title={value}>
|
||||
<span>
|
||||
{variablePath.map((item, index) => {
|
||||
return (
|
||||
<React.Fragment key={item}>
|
||||
{index ? ' / ' : ''}
|
||||
{item}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const Input = observer(_Input, { displayName: 'VariableInput' });
|
||||
|
@ -27,6 +27,7 @@ interface VariableContextValue {
|
||||
|
||||
interface VariableProviderProps {
|
||||
variableName: string;
|
||||
variableExampleValue?: any;
|
||||
variableType: string | null;
|
||||
openLastHelper?: boolean;
|
||||
children: React.ReactNode;
|
||||
@ -122,6 +123,7 @@ const _VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
variableName,
|
||||
children,
|
||||
variableType,
|
||||
variableExampleValue,
|
||||
openLastHelper,
|
||||
helperObservables,
|
||||
onVariableTemplateChange,
|
||||
@ -145,6 +147,10 @@ const _VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
useEffect(() => {
|
||||
async function fetchValue() {
|
||||
try {
|
||||
if (variableExampleValue !== undefined) {
|
||||
setValue(variableExampleValue);
|
||||
return;
|
||||
}
|
||||
const val = await getValue(variableName);
|
||||
if (val) {
|
||||
setValue(val);
|
||||
@ -157,7 +163,7 @@ const _VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
}
|
||||
}
|
||||
fetchValue();
|
||||
}, [localVariables, variableName, variables, getValue]);
|
||||
}, [localVariables, variableName, variables, getValue, variableExampleValue]);
|
||||
|
||||
const valueType =
|
||||
helperObservables.helpersObs.value.length > 0
|
||||
|
@ -0,0 +1,194 @@
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer, useField, useForm } from '@formily/react';
|
||||
import { AntdSchemaComponentProvider, FormItem, Plugin, SchemaComponent, Variable } 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 { dayjs } from '@nocobase/utils/client';
|
||||
import { Form } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
const parser = createJSONTemplateParser();
|
||||
|
||||
// 定义变量作用域
|
||||
const scope = [
|
||||
{
|
||||
label: 'Date',
|
||||
value: '$nDate',
|
||||
type: 'date',
|
||||
children: [
|
||||
{
|
||||
label: 'Now',
|
||||
value: 'now',
|
||||
type: 'date',
|
||||
tooltip: '当前时间:2025-03-26 10:00:00',
|
||||
},
|
||||
{
|
||||
label: 'Today',
|
||||
value: 'today',
|
||||
type: 'date',
|
||||
tooltip: '今天:2025-03-26',
|
||||
},
|
||||
{
|
||||
label: 'This Quarter',
|
||||
value: 'thisQuarter',
|
||||
type: 'date',
|
||||
tooltip: '本季度:2025Q1',
|
||||
},
|
||||
{
|
||||
label: 'This Week',
|
||||
value: 'thisWeek',
|
||||
type: 'date',
|
||||
tooltip: '本周:2025w12',
|
||||
},
|
||||
{
|
||||
label: 'Today (DateOnly)',
|
||||
value: 'today_dateOnly',
|
||||
type: 'date',
|
||||
tooltip: '今天(无时区):2025-03-26',
|
||||
},
|
||||
{
|
||||
label: 'Today (Without TZ)',
|
||||
value: 'today_withoutTz',
|
||||
type: 'date',
|
||||
tooltip: '今天(无时区时间):2025-03-26 00:00:00',
|
||||
},
|
||||
{
|
||||
label: 'Today (UTC)',
|
||||
value: 'today_utc',
|
||||
type: 'date',
|
||||
tooltip: '今天(UTC时间):2025-03-26T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// 定义表单属性
|
||||
const useFormBlockProps = () => {
|
||||
return {
|
||||
form: createForm({
|
||||
initialValues: {
|
||||
input1: '{{ $nDate.now }}',
|
||||
input2: '{{ $nDate.today | date_format: "YYYY-MM-DD" }}',
|
||||
input3: '{{ $nDate.today | date_add: 7, "day" | date_format: "YYYY-MM-DD" }}',
|
||||
input4: '{{ $nDate.today_dateOnly }}',
|
||||
input5: '{{ $nDate.today_withoutTz }}',
|
||||
input6: '{{ $nDate.today_utc }}',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
// 定义表单 schema
|
||||
const schema = {
|
||||
type: 'object',
|
||||
'x-component': 'FormV2',
|
||||
'x-use-component-props': 'useFormBlockProps',
|
||||
properties: {
|
||||
input1: {
|
||||
type: 'string',
|
||||
title: '基础变量选择',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.Input',
|
||||
'x-component-props': {
|
||||
scope,
|
||||
value: '{{ $nDate.now }}',
|
||||
},
|
||||
},
|
||||
input2: {
|
||||
type: 'string',
|
||||
title: '带格式化的变量',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.Input',
|
||||
'x-component-props': {
|
||||
scope,
|
||||
value: '{{ $nDate.today | date_format: "YYYY-MM-DD" }}',
|
||||
},
|
||||
},
|
||||
input3: {
|
||||
type: 'string',
|
||||
title: '带日期偏移的变量',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.Input',
|
||||
'x-component-props': {
|
||||
scope,
|
||||
value: '{{ $nDate.today | date_add: 7, "day" | date_format: "YYYY-MM-DD" }}',
|
||||
},
|
||||
},
|
||||
input4: {
|
||||
type: 'string',
|
||||
title: 'DateOnly 格式',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.Input',
|
||||
'x-component-props': {
|
||||
scope,
|
||||
value: '{{ $nDate.today_dateOnly }}',
|
||||
},
|
||||
},
|
||||
input5: {
|
||||
type: 'string',
|
||||
title: '无时区时间格式',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.Input',
|
||||
'x-component-props': {
|
||||
scope,
|
||||
value: '{{ $nDate.today_withoutTz }}',
|
||||
},
|
||||
},
|
||||
input6: {
|
||||
type: 'string',
|
||||
title: 'UTC 时间格式',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.Input',
|
||||
'x-component-props': {
|
||||
scope,
|
||||
value: '{{ $nDate.today_utc }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// 独立组件示例
|
||||
const Demo = () => {
|
||||
const [value1, setValue1] = useState('{{$nDate.now}}');
|
||||
const [value2, setValue2] = useState('{{$nDate.today | date_format: "YYYY-MM-DD"}}');
|
||||
const [value3, setValue3] = useState('{{$nDate.today | date_add: 7, "day" | date_format: "YYYY-MM-DD"}}');
|
||||
const [value4, setValue4] = useState('{{$nDate.today_dateOnly}}');
|
||||
const [value5, setValue5] = useState('{{$nDate.today_withoutTz}}');
|
||||
const [value6, setValue6] = useState('{{$nDate.today_utc}}');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form.Item label="基础变量选择">
|
||||
<Variable.Input scope={scope} value={value1} onChange={setValue1} />
|
||||
</Form.Item>
|
||||
<Form.Item label="带格式化的变量">
|
||||
<Variable.Input scope={scope} value={value2} onChange={setValue2} />
|
||||
</Form.Item>
|
||||
<Form.Item label="带日期偏移的变量">
|
||||
<Variable.Input scope={scope} value={value3} onChange={setValue3} />
|
||||
</Form.Item>
|
||||
<Form.Item label="DateOnly 格式">
|
||||
<Variable.Input scope={scope} value={value4} onChange={setValue4} />
|
||||
</Form.Item>
|
||||
<Form.Item label="无时区时间格式">
|
||||
<Variable.Input scope={scope} value={value5} onChange={setValue5} />
|
||||
</Form.Item>
|
||||
<Form.Item label="UTC 时间格式">
|
||||
<Variable.Input scope={scope} value={value6} onChange={setValue6} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 插件定义
|
||||
class DemoPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.router.add('root', { path: '/', Component: Demo });
|
||||
}
|
||||
}
|
||||
|
||||
// 创建应用实例
|
||||
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
|
||||
|
||||
export default app.getRootComponent();
|
@ -1,6 +1,6 @@
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer, useField, useForm } from '@formily/react';
|
||||
import { AntdSchemaComponentProvider, Plugin, SchemaComponent } from '@nocobase/client';
|
||||
import { AntdSchemaComponentProvider, Plugin, SchemaComponent, Variable } from '@nocobase/client';
|
||||
import { mockApp } from '@nocobase/client/demo-utils';
|
||||
import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client';
|
||||
import React from 'react';
|
||||
|
@ -0,0 +1,55 @@
|
||||
import { ISchema, Plugin, SchemaComponent, SchemaSettings } from '@nocobase/client';
|
||||
import { mockApp } from '@nocobase/client/demo-utils';
|
||||
import React from 'react';
|
||||
|
||||
const simpleSettings = new SchemaSettings({
|
||||
name: 'simpleSettings',
|
||||
items: [
|
||||
{
|
||||
name: 'delete',
|
||||
type: 'remove',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: 'root',
|
||||
'x-decorator': 'DndContext',
|
||||
'x-component': 'FormV2',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
title: 'Username',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-settings': 'simpleSettings',
|
||||
required: true,
|
||||
},
|
||||
nickname: {
|
||||
type: 'string',
|
||||
title: 'Nickname',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-settings': 'simpleSettings',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Demo = () => {
|
||||
return <SchemaComponent schema={schema} />;
|
||||
};
|
||||
|
||||
class DemoPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.router.add('root', { path: '/', Component: Demo });
|
||||
}
|
||||
}
|
||||
|
||||
const app = mockApp({
|
||||
designable: true,
|
||||
plugins: [DemoPlugin],
|
||||
schemaSettings: [simpleSettings],
|
||||
});
|
||||
|
||||
export default app.getRootComponent();
|
@ -0,0 +1,65 @@
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer, useField, useForm } from '@formily/react';
|
||||
import {
|
||||
AntdSchemaComponentProvider,
|
||||
FormItem,
|
||||
Plugin,
|
||||
SchemaComponent,
|
||||
useApp,
|
||||
useRequest,
|
||||
Variable,
|
||||
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 { dayjs } from '@nocobase/utils/client';
|
||||
import { Form } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
const scope = [{ label: 'now', value: 'now', type: 'date' }];
|
||||
|
||||
const Demo = () => {
|
||||
const app = useApp();
|
||||
const [value, setValue] = useState('{{now | date_format: "YYYY-MM-DD"}}');
|
||||
const [value2, setValue2] = useState('{{now |date_offset: "add", 1, "week" | date_format: "YYYY-MM-DD"}}');
|
||||
const tmplateData = { now: dayjs().format() };
|
||||
const { data } = useRequest(
|
||||
() => {
|
||||
return app.jsonTemplateParser.render(value, tmplateData);
|
||||
},
|
||||
{
|
||||
refreshDeps: [value],
|
||||
},
|
||||
);
|
||||
const { data: data2 } = useRequest(
|
||||
() => {
|
||||
return app.jsonTemplateParser.render(value2);
|
||||
},
|
||||
{
|
||||
refreshDeps: [value2],
|
||||
},
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<VariableEvaluateProvider data={tmplateData} context={{}}>
|
||||
<Form.Item label="format now" extra={`输出值${data}`}>
|
||||
<Variable.Input scope={scope} value={value} onChange={setValue}></Variable.Input>
|
||||
</Form.Item>
|
||||
<Form.Item label="offset and format" extra={`输出值${data2}`}>
|
||||
<Variable.Input scope={scope} value={value2} onChange={setValue2}></Variable.Input>
|
||||
</Form.Item>
|
||||
</VariableEvaluateProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class DemoPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.router.add('root', { path: '/', Component: Demo });
|
||||
}
|
||||
}
|
||||
|
||||
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
|
||||
|
||||
export default app.getRootComponent();
|
@ -0,0 +1,39 @@
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer, useField, useForm } from '@formily/react';
|
||||
import { AntdSchemaComponentProvider, FormItem, Plugin, SchemaComponent, Variable } 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 { dayjs } from '@nocobase/utils/client';
|
||||
import { Form } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
const scope = [
|
||||
{ label: 'v1', value: 'v1' },
|
||||
{ label: 'now', value: 'now', type: 'date' },
|
||||
];
|
||||
|
||||
const Demo = () => {
|
||||
const [value, setValue] = useState('{{now}}');
|
||||
const [value2, setValue2] = useState('{{v1}}');
|
||||
return (
|
||||
<div>
|
||||
<Form.Item label="date" extra="变量类型为 date,可以匹配 date 类型的 helpers">
|
||||
<Variable.Input scope={scope} value={value} onChange={setValue}></Variable.Input>
|
||||
</Form.Item>
|
||||
<Form.Item label="v1" extra="变量无类型,不能匹配任何 helpers">
|
||||
<Variable.Input scope={scope} value={value2} onChange={setValue2}></Variable.Input>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class DemoPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.router.add('root', { path: '/', Component: Demo });
|
||||
}
|
||||
}
|
||||
|
||||
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
|
||||
|
||||
export default app.getRootComponent();
|
@ -0,0 +1,74 @@
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer, useField, useForm } from '@formily/react';
|
||||
import { AntdSchemaComponentProvider, FormItem, Plugin, SchemaComponent, Variable } 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 { dayjs } from '@nocobase/utils/client';
|
||||
import { Form } from 'antd';
|
||||
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: 'Date',
|
||||
value: '$nDate',
|
||||
type: 'date',
|
||||
children: [{ label: 'now', value: 'now', type: 'date', disabled: true }],
|
||||
},
|
||||
];
|
||||
|
||||
const useFormBlockProps = () => {
|
||||
return {
|
||||
form: createForm({
|
||||
initialValues: {
|
||||
input: '{{ $nDate.now }}',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
'x-component': 'FormV2',
|
||||
'x-use-component-props': 'useFormBlockProps',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: `输入项`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.Input',
|
||||
'x-component-props': {
|
||||
scope,
|
||||
value: '{{ $nDate.now }}',
|
||||
test: '{{ $nDate.now }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Demo = () => {
|
||||
const [value, setValue] = useState('{{$nDate.now}}');
|
||||
const [value2, setValue2] = useState('{{$nDate.now | date_format: "YYYY-MM-DD"}}');
|
||||
return (
|
||||
<div>
|
||||
<Form.Item label="单变量">
|
||||
<Variable.Input scope={scope} value={value} onChange={setValue}></Variable.Input>
|
||||
</Form.Item>
|
||||
<Form.Item label="单变量 + helper">
|
||||
<Variable.Input scope={scope} value={value2} onChange={setValue2}></Variable.Input>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class DemoPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.router.add('root', { path: '/', Component: Demo });
|
||||
}
|
||||
}
|
||||
|
||||
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
|
||||
|
||||
export default app.getRootComponent();
|
@ -0,0 +1,53 @@
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer, useField, useForm } from '@formily/react';
|
||||
import {
|
||||
AntdSchemaComponentProvider,
|
||||
FormItem,
|
||||
Plugin,
|
||||
SchemaComponent,
|
||||
useApp,
|
||||
useRequest,
|
||||
Variable,
|
||||
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 { dayjs } from '@nocobase/utils/client';
|
||||
import { Form } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
const scope = [
|
||||
{ label: 'v1', value: 'v1' },
|
||||
{ label: 'v2', value: 'v2', example: 'example-value-v2' },
|
||||
];
|
||||
|
||||
const Demo = () => {
|
||||
const app = useApp();
|
||||
const [value, setValue] = useState('{{v1}}');
|
||||
const [value2, setValue2] = useState('{{v2}}');
|
||||
const tmplateData = { v1: 'value-v1' };
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VariableEvaluateProvider data={tmplateData} context={{}}>
|
||||
<Form.Item label="变量 v1" extra={`鼠标在变量上悬浮即可显示变量值`}>
|
||||
<Variable.Input scope={scope} value={value} onChange={setValue}></Variable.Input>
|
||||
</Form.Item>
|
||||
<Form.Item label="变量 v2" extra={`鼠标在变量上悬浮即可显示变量值`}>
|
||||
<Variable.Input scope={scope} value={value2} onChange={setValue2}></Variable.Input>
|
||||
</Form.Item>
|
||||
</VariableEvaluateProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class DemoPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.router.add('root', { path: '/', Component: Demo });
|
||||
}
|
||||
}
|
||||
|
||||
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
|
||||
|
||||
export default app.getRootComponent();
|
@ -4,6 +4,42 @@
|
||||
|
||||
## `Variable.Input`
|
||||
|
||||
### scope 定义
|
||||
|
||||
scope 可以定义变量的 label, value , type
|
||||
变量的 type 会匹配对应的 helper 函数,对变量进行二次处理。目前内置了 date.format , date.offset 的 helpers。也就是说如果变量的类型是 date, 会匹配到对应的过滤器。如果变量是其他类型或者无类型,都无法匹配到过滤器。
|
||||
|
||||
|
||||
```ts
|
||||
const scope = [
|
||||
{label: 'v1', value: 'v1'}
|
||||
{label: 'now', value: 'now', type: 'date'}
|
||||
]
|
||||
```
|
||||
|
||||
<code src="./demos/scope.tsx"></code>
|
||||
|
||||
### helper 示例
|
||||
<code src="./demos/helper-demo.tsx"></code>
|
||||
|
||||
### 变量值如何获取
|
||||
1. VariableEvaluateProvider 上下文获取
|
||||
提供 VariableEvaluateProvider 上下文,可以注入 data 属性
|
||||
2. 从 scope 中的 example 属性获取
|
||||
某些变量在前端无法获取到实际值(如服务端环境下的变量),但是有需要提供一个示例值方便应用 helpers,可以在声明 scope 选项时带上示例值,如 `{label: 'v1' , value: 'v1', example: 'example-value-v1'}`
|
||||
|
||||
一下是一个完整的例子,变量 v1 由上下文提供值,变量 v2 由 example 提供值。鼠标悬浮在变量名称上即可查看对应变量的值。
|
||||
<code src="./demos/variable-value.tsx"></code>
|
||||
|
||||
### 应用场景
|
||||
#### 默认值
|
||||
<code src="./demos/form-default-value.tsx"></code>
|
||||
|
||||
### 变量选中并已被禁用的效果
|
||||
当变量被禁用时,不影响已经选中的变量显示。在下面的例子中,now 变量已被禁用:
|
||||
|
||||
<code src="./demos/selected-and-disable.tsx"></code>
|
||||
|
||||
```ts
|
||||
import type { DefaultOptionType } from 'antd/lib/cascader';
|
||||
type VariableInputProps = {
|
||||
|
@ -48,9 +48,10 @@ const getValue = async (params: {
|
||||
}): 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 dataKey = path.slice(1).join('.');
|
||||
const scopeKey = path[0];
|
||||
const scopeFn = data[scopeKey];
|
||||
if (typeof scopeFn === 'function') {
|
||||
@ -62,11 +63,10 @@ const getValue = async (params: {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = get(data, dataKey);
|
||||
|
||||
// Handle function values
|
||||
const value = get(data, field);
|
||||
if (typeof value === 'function') {
|
||||
return value();
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
@ -68,9 +68,11 @@ export class JSONTemplateParser {
|
||||
}
|
||||
|
||||
registerFilterGroup(group: FilterGroup): void {
|
||||
if (this._filterGroups.find((g) => g.name === group.name)) return;
|
||||
this._filterGroups.push(group);
|
||||
}
|
||||
registerFilter(filter: Helper): void {
|
||||
if (this._filters.find((f) => f.name === filter.name)) return;
|
||||
this._filters.push(filter);
|
||||
this._engine.registerFilter(filter.name, filter.handler);
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ type ValueType = 'date' | 'string' | 'dateRange' | 'any';
|
||||
export type Helper = {
|
||||
name: string;
|
||||
title: string;
|
||||
handler: Function;
|
||||
handler: (...args: any[]) => any;
|
||||
group: string;
|
||||
inputType: ValueType;
|
||||
outputType: ValueType;
|
||||
|
@ -70,3 +70,15 @@ export function dateSubtract(initialValue: any, unit: any, number: number) {
|
||||
return handler(initialValue);
|
||||
}
|
||||
}
|
||||
|
||||
export function dateOffset(initialValue: any, action: 'add' | 'subtract', number: number, unit: any) {
|
||||
const handler = (value: any) => {
|
||||
if (action === 'add') {
|
||||
return dayjs.isDayjs(value) ? value.add(number, unit) : dayjs(value).add(number, unit);
|
||||
} else if (action === 'subtract') {
|
||||
return dayjs.isDayjs(value) ? value.subtract(number, unit) : dayjs(value).subtract(number, unit);
|
||||
}
|
||||
throw new Error('Invalid action');
|
||||
};
|
||||
return handler(initialValue);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
import { Helper } from '@nocobase/json-template-parser';
|
||||
import { first } from './array';
|
||||
import { dateAdd, dateFormat, dateSubtract } from './date';
|
||||
import { dateAdd, dateFormat, dateOffset, dateSubtract } from './date';
|
||||
const NAMESPACE = 'variable-helpers';
|
||||
|
||||
function tval(text: string) {
|
||||
@ -37,50 +37,33 @@ export const helpers: Helper[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'date_add',
|
||||
title: 'add',
|
||||
handler: dateAdd,
|
||||
group: 'date',
|
||||
inputType: 'date',
|
||||
outputType: 'date',
|
||||
sort: 2,
|
||||
args: [],
|
||||
uiSchema: [
|
||||
{
|
||||
name: 'unit',
|
||||
title: tval('Unit'),
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: tval('Year'), value: 'year' },
|
||||
{ label: tval('Quarter'), value: 'quarter' },
|
||||
{ label: tval('Week'), value: 'week' },
|
||||
{ label: tval('Day'), value: 'day' },
|
||||
{ label: tval('Hour'), value: 'hour' },
|
||||
{ label: tval('Minute'), value: 'minute' },
|
||||
{ label: tval('Second'), value: 'second' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'number',
|
||||
title: tval('Amount'),
|
||||
type: 'number',
|
||||
'x-component': 'InputNumber',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'date_subtract',
|
||||
title: 'substract',
|
||||
handler: dateSubtract,
|
||||
name: 'date_offset',
|
||||
title: 'offset',
|
||||
handler: dateOffset,
|
||||
group: 'date',
|
||||
inputType: 'date',
|
||||
outputType: 'date',
|
||||
sort: 3,
|
||||
args: [],
|
||||
uiSchema: [
|
||||
{
|
||||
name: 'action',
|
||||
title: tval('Action'),
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: tval('Add'), value: 'add' },
|
||||
{ label: tval('Sbutract'), value: 'subtract' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'number',
|
||||
title: tval('Amount'),
|
||||
type: 'number',
|
||||
'x-component': 'InputNumber',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'unit',
|
||||
title: tval('Unit'),
|
||||
@ -97,35 +80,8 @@ export const helpers: Helper[] = [
|
||||
{ label: tval('Second'), value: 'second' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'number',
|
||||
title: tval('Amount'),
|
||||
type: 'number',
|
||||
'x-component': 'InputNumber',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'array_first',
|
||||
title: 'first',
|
||||
handler: first,
|
||||
sort: 4,
|
||||
args: [],
|
||||
group: 'array',
|
||||
inputType: 'string',
|
||||
outputType: 'any',
|
||||
},
|
||||
{
|
||||
name: 'array_last',
|
||||
title: 'last',
|
||||
sort: 5,
|
||||
args: [],
|
||||
handler: first,
|
||||
group: 'array',
|
||||
inputType: 'string',
|
||||
outputType: 'any',
|
||||
},
|
||||
];
|
||||
|
||||
export const helperGroups = [
|
||||
|
Loading…
x
Reference in New Issue
Block a user