mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-07 14:39:25 +08:00
feat: update type to helpers
This commit is contained in:
parent
972a7404b7
commit
313f8f40bf
@ -1,194 +0,0 @@
|
||||
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,192 +0,0 @@
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer, useField, useForm } from '@formily/react';
|
||||
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 { 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',
|
||||
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 = () => {
|
||||
return {
|
||||
form: createForm({
|
||||
initialValues: {},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
output: {
|
||||
type: 'void',
|
||||
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>{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>
|
||||
<VariableEvaluateProvider data={{ $nDate: dateScopeFn }} context={{}}>
|
||||
<SchemaComponent schema={schema} scope={{ useFormBlockProps }} components={{ OutPut, Result }} />
|
||||
</VariableEvaluateProvider>
|
||||
</AntdSchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
|
||||
class DemoPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.router.add('root', { path: '/', Component: Demo });
|
||||
}
|
||||
}
|
||||
|
||||
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
|
||||
|
||||
export default app.getRootComponent();
|
@ -17,7 +17,7 @@ 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 scope = [{ label: 'now', value: 'now', helpers: ['date.*'] }];
|
||||
|
||||
const Demo = () => {
|
||||
const app = useApp();
|
||||
|
@ -14,21 +14,10 @@ const scope = [
|
||||
{
|
||||
label: 'Date',
|
||||
value: '$nDate',
|
||||
type: 'date',
|
||||
children: [{ label: 'now', value: 'now', type: 'date', disabled: true }],
|
||||
children: [{ label: 'now', value: 'now', helpers: ['date.*'], disabled: true }],
|
||||
},
|
||||
];
|
||||
|
||||
const useFormBlockProps = () => {
|
||||
return {
|
||||
form: createForm({
|
||||
initialValues: {
|
||||
input: '{{ $nDate.now }}',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
'x-component': 'FormV2',
|
||||
|
@ -1,42 +1,123 @@
|
||||
# Variable
|
||||
|
||||
Variable selector.
|
||||
变量选择器组件,用于在表单和配置中选择和设置变量。
|
||||
|
||||
## `Variable.Input`
|
||||
|
||||
### 默认值模式
|
||||
### Scope 定义
|
||||
|
||||
Scope 用于定义变量的基本属性,包括:
|
||||
- `label`: 显示名称
|
||||
- `value`: 变量值
|
||||
- `helpers`: 支持的 helpers 配置规则
|
||||
|
||||
变量的 `helpers` 属性会匹配对应的 helper 函数,用于对变量进行二次处理。目前内置了以下 helper:
|
||||
- `date.date_format`: 日期格式化
|
||||
- `date.date_calculate`: 日期计算
|
||||
|
||||
|
||||
```ts
|
||||
const scope = [
|
||||
{label: 'v1', value: 'v1'}
|
||||
{label: 'now', value: 'now', helpers: ['date.*']}
|
||||
]
|
||||
```
|
||||
|
||||
<code src="./demos/scope.tsx"></code>
|
||||
|
||||
### Helper 示例
|
||||
|
||||
目前支持两个内置的 helper 函数:
|
||||
|
||||
1. **date_format**
|
||||
- 用于格式化日期变量
|
||||
- 支持常见的日期格式,如 'YYYY-MM-DD'、'YYYY-MM-DD HH:mm:ss' 等
|
||||
- 示例:`{{$date.now | date.format 'YYYY-MM-DD'}}`
|
||||
|
||||
2. **date_calculate**
|
||||
- 用于对日期进行计算计算
|
||||
- 支持年、月、日、时、分、秒的计算
|
||||
- 示例:`{{$date.now | date.calculate '1d'}}`(向后计算1天)
|
||||
|
||||
<code src="./demos/helper-demo.tsx"></code>
|
||||
|
||||
### 变量值获取方式
|
||||
|
||||
变量值可以通过以下两种方式获取:
|
||||
|
||||
1. **VariableEvaluateProvider 上下文**
|
||||
- 通过提供 VariableEvaluateProvider 上下文
|
||||
- 可以注入 data 属性来获取变量值
|
||||
|
||||
2. **Scope 中的 example 属性**
|
||||
- 适用于前端无法获取实际值的场景(如服务端环境下的变量)
|
||||
- 通过 example 属性提供示例值,方便应用 helpers
|
||||
- 示例:`{label: 'v1', value: 'v1', example: 'example-value-v1'}`
|
||||
|
||||
下面的示例展示了两种方式:
|
||||
- 变量 v1 通过上下文获取值
|
||||
- 变量 v2 通过 example 获取值
|
||||
- 鼠标悬浮在变量名称上可查看对应变量的值
|
||||
|
||||
<code src="./demos/variable-value.tsx"></code>
|
||||
|
||||
### 应用场景
|
||||
|
||||
#### 1. 默认值设置
|
||||
根据字段类型的不同,同一个变量可能需要不同的值。以"今天"为例:
|
||||
- `dateOnly` 类型:输出 `2025-01-01`
|
||||
- 无时区 `datetime`:输出 `2025-01-01 00:00:00`
|
||||
- 有时区 `datetime`:输出 `2024-12-31T16:00:00.000Z`
|
||||
|
||||
因此需要提供三个变量:
|
||||
- `today_withtz`
|
||||
- `today_withouttz`
|
||||
- `today_dateonly`
|
||||
|
||||
<code src="./demos/form-default-value.tsx"></code>
|
||||
|
||||
#### 2. Filter 组件
|
||||
<code src="./demos/filter-demo.tsx"></code>
|
||||
|
||||
### 变量禁用状态
|
||||
当变量被禁用时,已选中的变量值仍然保持显示。下面的示例展示了 `now` 变量被禁用的情况:
|
||||
|
||||
<code src="./demos/selected-and-disable.tsx"></code>
|
||||
|
||||
### 组件属性
|
||||
|
||||
```ts
|
||||
import type { DefaultOptionType } from 'antd/lib/cascader';
|
||||
type VariableInputProps = {
|
||||
value?: string;
|
||||
scope?: DefaultOptionType[] | (() => DefaultOptionType[]);
|
||||
onChange: (value: string, optionPath?: any[]) => void;
|
||||
children?: any;
|
||||
button?: React.ReactElement;
|
||||
useTypedConstant?: true | string[];
|
||||
changeOnSelect?: CascaderProps['changeOnSelect'];
|
||||
fieldNames?: CascaderProps['fieldNames'];
|
||||
disabled?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
parseOptions?: ParseOptions;
|
||||
|
||||
type VariableInputProps = {
|
||||
value?: string; // 变量值
|
||||
scope?: DefaultOptionType[] | (() => DefaultOptionType[]); // 变量范围
|
||||
onChange: (value: string, optionPath?: any[]) => void; // 值变化回调
|
||||
children?: any; // 子组件
|
||||
button?: React.ReactElement; // 自定义按钮
|
||||
useTypedConstant?: true | string[]; // 使用类型常量
|
||||
changeOnSelect?: CascaderProps['changeOnSelect']; // 选择时是否触发变化
|
||||
fieldNames?: CascaderProps['fieldNames']; // 字段名称映射
|
||||
disabled?: boolean; // 是否禁用
|
||||
style?: React.CSSProperties; // 样式
|
||||
className?: string; // 类名
|
||||
parseOptions?: ParseOptions; // 解析选项
|
||||
}
|
||||
|
||||
type ParseOptions = {
|
||||
stringToDate?: boolean;
|
||||
stringToDate?: boolean; // 是否将字符串转换为日期
|
||||
};
|
||||
```
|
||||
|
||||
<code src="./demos/demo1.tsx"></code>
|
||||
|
||||
### 数据范围模式
|
||||
数据范围的模式
|
||||
### 变量上下文
|
||||
|
||||
### `Variable.TextArea`
|
||||
同一个变量在不同的运行时环境可能有不同的值,因此需要为变量提供上下文环境。
|
||||
|
||||
## `Variable.TextArea`
|
||||
|
||||
<code src="./demos/demo2.tsx"></code>
|
||||
|
||||
### `Variable.JSON`
|
||||
## `Variable.JSON`
|
||||
|
||||
<code src="./demos/demo3.tsx"></code>
|
||||
|
@ -9,18 +9,17 @@
|
||||
Scope 用于定义变量的基本属性,包括:
|
||||
- `label`: 显示名称
|
||||
- `value`: 变量值
|
||||
- `type`: 变量类型
|
||||
- `helpers`: 支持的 helpers 配置规则
|
||||
|
||||
变量的 `type` 属性会匹配对应的 helper 函数,用于对变量进行二次处理。目前内置了以下 helper:
|
||||
变量的 `helpers` 属性会匹配对应的 helper 函数,用于对变量进行二次处理。目前内置了以下 helper:
|
||||
- `date.date_format`: 日期格式化
|
||||
- `date.date_calculate`: 日期计算
|
||||
|
||||
只有类型为 `date` 的变量才能匹配到对应的过滤器,其他类型或无类型的变量无法使用这些过滤器。
|
||||
|
||||
```ts
|
||||
const scope = [
|
||||
{label: 'v1', value: 'v1'}
|
||||
{label: 'now', value: 'now', type: 'date'}
|
||||
{label: 'now', value: 'now', helpers: ['date.*']}
|
||||
]
|
||||
```
|
||||
|
||||
@ -80,9 +79,6 @@ const scope = [
|
||||
#### 2. Filter 组件
|
||||
<code src="./demos/filter-demo.tsx"></code>
|
||||
|
||||
<!-- #### 2. 数据范围
|
||||
<code src="./demos/data-scope-demo.tsx"></code> -->
|
||||
|
||||
### 变量禁用状态
|
||||
当变量被禁用时,已选中的变量值仍然保持显示。下面的示例展示了 `now` 变量被禁用的情况:
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user