mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
feat: variable supported helpers
This commit is contained in:
parent
dd2c1b64c6
commit
22a9f2bd6e
158
docs/zh-CN/developer/filter-operators/variable-filter-mapping.md
Normal file
158
docs/zh-CN/developer/filter-operators/variable-filter-mapping.md
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
# VariableFilterMapping 使用指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
VariableFilterMapping 是 NocoBase 中一个用于处理变量助手条件映射的工具类。它允许开发者定义变量与助手条件之间的映射关系,使系统能够根据变量值动态生成数据查询条件。
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
- 将变量值转换为数据查询条件
|
||||||
|
- 支持多种助手操作符
|
||||||
|
- 处理不同类型的变量(字符串、数字、日期等)
|
||||||
|
- 支持自定义变量处理器
|
||||||
|
|
||||||
|
## 基本用法
|
||||||
|
|
||||||
|
### 1. 定义助手条件映射
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { VariableFilterMapping } from '@nocobase/database';
|
||||||
|
|
||||||
|
const helperMapping = new VariableFilterMapping({
|
||||||
|
'user.id': '$user.id', // 映射用户ID
|
||||||
|
'user.name': '$user.name', // 映射用户名
|
||||||
|
'created_at': '$dateRange', // 映射日期范围
|
||||||
|
'status': '$status' // 映射状态
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用映射生成助手条件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 变量值
|
||||||
|
const variables = {
|
||||||
|
'$user.id': 1,
|
||||||
|
'$user.name': 'admin',
|
||||||
|
'$dateRange': ['2023-01-01', '2023-12-31'],
|
||||||
|
'$status': 'active'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成助手条件
|
||||||
|
const helper = helperMapping.toHelper(variables);
|
||||||
|
```
|
||||||
|
|
||||||
|
生成的助手条件类似于:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
'user.id': 1,
|
||||||
|
'user.name': 'admin',
|
||||||
|
'created_at': {
|
||||||
|
$between: ['2023-01-01', '2023-12-31']
|
||||||
|
},
|
||||||
|
'status': 'active'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法
|
||||||
|
|
||||||
|
### 1. 自定义操作符
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const helperMapping = new VariableFilterMapping({
|
||||||
|
'price': {
|
||||||
|
name: '$price',
|
||||||
|
operator: '$gt' // 使用大于操作符
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const helper = helperMapping.toHelper({
|
||||||
|
'$price': 100
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成: { price: { $gt: 100 } }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 嵌套映射
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const helperMapping = new VariableFilterMapping({
|
||||||
|
'product': {
|
||||||
|
'price': {
|
||||||
|
name: '$product.price',
|
||||||
|
operator: '$between'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const helper = helperMapping.toHelper({
|
||||||
|
'$product.price': [100, 200]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成: { product: { price: { $between: [100, 200] } } }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用处理器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const helperMapping = new VariableFilterMapping({
|
||||||
|
'tags': {
|
||||||
|
name: '$tags',
|
||||||
|
operator: '$match',
|
||||||
|
processor: (value) => {
|
||||||
|
// 将逗号分隔的标签转换为数组
|
||||||
|
return value.split(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const helper = helperMapping.toHelper({
|
||||||
|
'$tags': 'javascript,typescript,react'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成: { tags: { $match: ['javascript', 'typescript', 'react'] } }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实际应用场景
|
||||||
|
|
||||||
|
1. **数据表格筛选**:根据用户界面中的筛选选项动态生成查询条件
|
||||||
|
2. **权限控制**:基于当前用户信息生成数据访问限制
|
||||||
|
3. **报表生成**:使用用户选择的参数动态生成报表数据查询条件
|
||||||
|
4. **工作流触发条件**:定义基于变量的工作流触发条件
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 变量名必须以 `$` 开头
|
||||||
|
- 确保变量值与期望的操作符兼容(例如,$between 操作符需要数组值)
|
||||||
|
- 注意处理变量值为 null 或 undefined 的情况
|
||||||
|
- 复杂的助手逻辑可能需要使用处理器函数进行转换
|
||||||
|
|
||||||
|
## API 参考
|
||||||
|
|
||||||
|
### VariableFilterMapping 构造函数
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
constructor(options: Record<string, MappingOptions | string>)
|
||||||
|
```
|
||||||
|
|
||||||
|
### MappingOptions 接口
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface MappingOptions {
|
||||||
|
name: string; // 变量名
|
||||||
|
operator?: string; // 操作符名称
|
||||||
|
processor?: (value) => any; // 值处理器
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### toHelper 方法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
toHelper(variables: Record<string, any>): Record<string, any>
|
||||||
|
```
|
||||||
|
|
||||||
|
将变量值转换为助手条件。
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
VariableFilterMapping 是 NocoBase 中强大的查询条件生成工具,它通过定义变量与助手条件的映射关系,使应用能够基于动态变量生成灵活的数据查询条件。掌握这一工具可以帮助开发者更有效地实现动态数据筛选、权限控制和自定义业务逻辑。
|
@ -19,6 +19,48 @@ import { useApp } from '../../../../application';
|
|||||||
import { SchemaComponent } from '../../../core/SchemaComponent';
|
import { SchemaComponent } from '../../../core/SchemaComponent';
|
||||||
import { useVariable } from '../VariableProvider';
|
import { useVariable } from '../VariableProvider';
|
||||||
import { helpersObs, rawHelpersObs, removeHelper } from './observables';
|
import { helpersObs, rawHelpersObs, removeHelper } from './observables';
|
||||||
|
import { VariableHelperMapping } from '../Input';
|
||||||
|
import minimatch from 'minimatch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes special glob characters in a string
|
||||||
|
* @param str The string to escape
|
||||||
|
* @returns The escaped string
|
||||||
|
*/
|
||||||
|
function escapeGlob(str: string): string {
|
||||||
|
return str.replace(/[?*[\](){}!|+@\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if a filter is allowed for a given variable based on the variableHelperMapping configuration
|
||||||
|
* @param variableName The name of the variable to test
|
||||||
|
* @param filterName The name of the filter to test
|
||||||
|
* @param mapping The variable helper mapping configuration
|
||||||
|
* @returns boolean indicating if the filter is allowed for the variable
|
||||||
|
*/
|
||||||
|
export function isFilterAllowedForVariable(
|
||||||
|
variableName: string,
|
||||||
|
filterName: string,
|
||||||
|
mapping?: VariableHelperMapping,
|
||||||
|
): boolean {
|
||||||
|
if (!mapping?.rules) {
|
||||||
|
return true; // If no rules defined, allow all filters
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each rule
|
||||||
|
for (const rule of mapping.rules) {
|
||||||
|
// Check if variable matches the pattern
|
||||||
|
// We don't escape the pattern since it's meant to be a glob pattern
|
||||||
|
// But we escape the variable name since it's a literal value
|
||||||
|
if (minimatch(escapeGlob(variableName), rule.variables)) {
|
||||||
|
// Check if filter matches any of the allowed patterns
|
||||||
|
return rule.filters.some((pattern) => minimatch(filterName, pattern));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no matching rule found and strictMode is true, deny the filter
|
||||||
|
return !mapping.strictMode;
|
||||||
|
}
|
||||||
|
|
||||||
export const HelperConfiguator = observer(
|
export const HelperConfiguator = observer(
|
||||||
({ index, close }: { index: number; close: () => void }) => {
|
({ index, close }: { index: number; close: () => void }) => {
|
||||||
|
@ -12,7 +12,6 @@ import { css, cx } from '@emotion/css';
|
|||||||
import { autorun } from '@formily/reactive';
|
import { autorun } from '@formily/reactive';
|
||||||
import { useForm, observer } from '@formily/react';
|
import { useForm, observer } from '@formily/react';
|
||||||
import { error } from '@nocobase/utils/client';
|
import { error } from '@nocobase/utils/client';
|
||||||
import { cloneDeep } from 'lodash';
|
|
||||||
import { extractTemplateElements, composeTemplate } from '@nocobase/json-template-parser';
|
import { extractTemplateElements, composeTemplate } from '@nocobase/json-template-parser';
|
||||||
import {
|
import {
|
||||||
Input as AntInput,
|
Input as AntInput,
|
||||||
@ -46,6 +45,23 @@ type ParseOptions = {
|
|||||||
stringToDate?: boolean;
|
stringToDate?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for mapping variables to their allowed filter functions
|
||||||
|
*/
|
||||||
|
interface VariableHelperRule {
|
||||||
|
/** Pattern to match variables, supports glob patterns */
|
||||||
|
variables: string;
|
||||||
|
/** Array of allowed filter patterns, supports glob patterns */
|
||||||
|
filters: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VariableHelperMapping {
|
||||||
|
/** Array of rules defining which filters are allowed for which variables */
|
||||||
|
rules: VariableHelperRule[];
|
||||||
|
/** Optional flag to determine if unlisted combinations should be allowed */
|
||||||
|
strictMode?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
function parseValue(value: any, options: ParseOptions = {}): string | string[] {
|
function parseValue(value: any, options: ParseOptions = {}): string | string[] {
|
||||||
if (value == null || (Array.isArray(value) && value.length === 0)) {
|
if (value == null || (Array.isArray(value) && value.length === 0)) {
|
||||||
return 'null';
|
return 'null';
|
||||||
@ -200,6 +216,7 @@ export type VariableInputProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
parseOptions?: ParseOptions;
|
parseOptions?: ParseOptions;
|
||||||
hideVariableButton?: boolean;
|
hideVariableButton?: boolean;
|
||||||
|
variableHelperMapping?: VariableHelperMapping;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Input(props: VariableInputProps) {
|
export function Input(props: VariableInputProps) {
|
||||||
|
@ -14,7 +14,7 @@ interface VariableContextValue {
|
|||||||
value: any;
|
value: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface eProviderProps {
|
interface VariableProviderProps {
|
||||||
variableName: string;
|
variableName: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,23 @@ import { useCurrentUserVariable } from './hooks/useUserVariable';
|
|||||||
import { useVariableOptions } from './hooks/useVariableOptions';
|
import { useVariableOptions } from './hooks/useVariableOptions';
|
||||||
import { Option } from './type';
|
import { Option } from './type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for mapping variables to their allowed filter functions
|
||||||
|
*/
|
||||||
|
interface VariableFilterRule {
|
||||||
|
/** Pattern to match variables, supports glob patterns */
|
||||||
|
variables: string;
|
||||||
|
/** Array of allowed filter patterns, supports glob patterns */
|
||||||
|
filters: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VariableFilterMapping {
|
||||||
|
/** Array of rules defining which filters are allowed for which variables */
|
||||||
|
rules: VariableFilterRule[];
|
||||||
|
/** Optional flag to determine if unlisted combinations should be allowed */
|
||||||
|
strictMode?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface GetShouldChangeProps {
|
interface GetShouldChangeProps {
|
||||||
collectionField: CollectionFieldOptions_deprecated;
|
collectionField: CollectionFieldOptions_deprecated;
|
||||||
variables: VariablesContextType;
|
variables: VariablesContextType;
|
||||||
@ -43,6 +60,8 @@ type Props = {
|
|||||||
onChange: (value: any, optionPath?: any[]) => void;
|
onChange: (value: any, optionPath?: any[]) => void;
|
||||||
renderSchemaComponent: (props: RenderSchemaComponentProps) => any;
|
renderSchemaComponent: (props: RenderSchemaComponentProps) => any;
|
||||||
schema?: any;
|
schema?: any;
|
||||||
|
/** Configuration for mapping variables to their allowed filter functions */
|
||||||
|
variableFilterMapping?: VariableFilterMapping;
|
||||||
/** 消费变量值的字段 */
|
/** 消费变量值的字段 */
|
||||||
targetFieldSchema?: Schema;
|
targetFieldSchema?: Schema;
|
||||||
children?: any;
|
children?: any;
|
||||||
@ -344,3 +363,37 @@ export function useCompatOldVariables(props: {
|
|||||||
|
|
||||||
return { compatOldVariables };
|
return { compatOldVariables };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a variable and filter combination is allowed according to the mapping rules
|
||||||
|
*/
|
||||||
|
function isFilterAllowedForVariable(
|
||||||
|
variable: string,
|
||||||
|
filter: string,
|
||||||
|
rules: VariableFilterRule[],
|
||||||
|
strictMode = false,
|
||||||
|
): boolean {
|
||||||
|
// If no rules defined and not in strict mode, allow everything
|
||||||
|
if (!rules?.length && !strictMode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rule of rules) {
|
||||||
|
const variablePattern = new RegExp('^' + rule.variables.replace(/\*/g, '.*') + '$');
|
||||||
|
if (variablePattern.test(variable)) {
|
||||||
|
// If no filters defined for this rule, allow all helpers
|
||||||
|
if (!rule.filters?.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any of the filter patterns match the helper
|
||||||
|
return rule.filters.some((filter) => {
|
||||||
|
const filterPattern = new RegExp('^' + filter.replace(/\*/g, '.*') + '$');
|
||||||
|
return filterPattern.test(filter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no matching rules found, return !strictMode
|
||||||
|
return !strictMode;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user