diff --git a/packages/core/client/src/schema-component/antd/variable/demos/comprehensive-demo.tsx b/packages/core/client/src/schema-component/antd/variable/demos/comprehensive-demo.tsx deleted file mode 100644 index 03c3ee90e0..0000000000 --- a/packages/core/client/src/schema-component/antd/variable/demos/comprehensive-demo.tsx +++ /dev/null @@ -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 ( -
- - - - - - - - - - - - - - - - - - -
- ); -}; - -// 插件定义 -class DemoPlugin extends Plugin { - async load() { - this.app.router.add('root', { path: '/', Component: Demo }); - } -} - -// 创建应用实例 -const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] }); - -export default app.getRootComponent(); diff --git a/packages/core/client/src/schema-component/antd/variable/demos/demo1.tsx b/packages/core/client/src/schema-component/antd/variable/demos/demo1.tsx deleted file mode 100644 index ac2f646761..0000000000 --- a/packages/core/client/src/schema-component/antd/variable/demos/demo1.tsx +++ /dev/null @@ -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
{form.values.input}
; -}); - -const Result = observer(() => { - const form = useForm(); - const [value, setValue] = useState(''); - 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
{value.toString()}
; -}); - -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 ( - - - - - - ); -}; - -class DemoPlugin extends Plugin { - async load() { - this.app.router.add('root', { path: '/', Component: Demo }); - } -} - -const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] }); - -export default app.getRootComponent(); diff --git a/packages/core/client/src/schema-component/antd/variable/demos/helper-demo.tsx b/packages/core/client/src/schema-component/antd/variable/demos/helper-demo.tsx index cb819e731f..d32f2569c7 100644 --- a/packages/core/client/src/schema-component/antd/variable/demos/helper-demo.tsx +++ b/packages/core/client/src/schema-component/antd/variable/demos/helper-demo.tsx @@ -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(); diff --git a/packages/core/client/src/schema-component/antd/variable/demos/selected-and-disable.tsx b/packages/core/client/src/schema-component/antd/variable/demos/selected-and-disable.tsx index 1ac5b0f57f..d895a88166 100644 --- a/packages/core/client/src/schema-component/antd/variable/demos/selected-and-disable.tsx +++ b/packages/core/client/src/schema-component/antd/variable/demos/selected-and-disable.tsx @@ -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', diff --git a/packages/core/client/src/schema-component/antd/variable/index.en-US.md b/packages/core/client/src/schema-component/antd/variable/index.en-US.md index 6688e7508b..756c2c54c1 100644 --- a/packages/core/client/src/schema-component/antd/variable/index.en-US.md +++ b/packages/core/client/src/schema-component/antd/variable/index.en-US.md @@ -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.*']} +] +``` + + + +### 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天) + + + +### 变量值获取方式 + +变量值可以通过以下两种方式获取: + +1. **VariableEvaluateProvider 上下文** + - 通过提供 VariableEvaluateProvider 上下文 + - 可以注入 data 属性来获取变量值 + +2. **Scope 中的 example 属性** + - 适用于前端无法获取实际值的场景(如服务端环境下的变量) + - 通过 example 属性提供示例值,方便应用 helpers + - 示例:`{label: 'v1', value: 'v1', example: 'example-value-v1'}` + +下面的示例展示了两种方式: +- 变量 v1 通过上下文获取值 +- 变量 v2 通过 example 获取值 +- 鼠标悬浮在变量名称上可查看对应变量的值 + + + +### 应用场景 + +#### 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` + + + +#### 2. Filter 组件 + + +### 变量禁用状态 +当变量被禁用时,已选中的变量值仍然保持显示。下面的示例展示了 `now` 变量被禁用的情况: + + + +### 组件属性 ```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; // 是否将字符串转换为日期 }; ``` - -### 数据范围模式 -数据范围的模式 +### 变量上下文 -### `Variable.TextArea` +同一个变量在不同的运行时环境可能有不同的值,因此需要为变量提供上下文环境。 + +## `Variable.TextArea` -### `Variable.JSON` +## `Variable.JSON` diff --git a/packages/core/client/src/schema-component/antd/variable/index.md b/packages/core/client/src/schema-component/antd/variable/index.md index 097dbf357e..756c2c54c1 100644 --- a/packages/core/client/src/schema-component/antd/variable/index.md +++ b/packages/core/client/src/schema-component/antd/variable/index.md @@ -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 组件 - - ### 变量禁用状态 当变量被禁用时,已选中的变量值仍然保持显示。下面的示例展示了 `now` 变量被禁用的情况: