feat: update type to helpers

This commit is contained in:
Sheldon Guo 2025-04-02 12:16:29 +08:00
parent 972a7404b7
commit 313f8f40bf
6 changed files with 107 additions and 427 deletions

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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',

View File

@ -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>

View File

@ -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` 变量被禁用的情况: