diff --git a/packages/core/acl/src/acl.ts b/packages/core/acl/src/acl.ts index 0474d3476..c93ad663b 100644 --- a/packages/core/acl/src/acl.ts +++ b/packages/core/acl/src/acl.ts @@ -1,7 +1,6 @@ import { Action } from '@nocobase/resourcer'; -import { assign, Toposort, ToposortOptions } from '@nocobase/utils'; +import { assign, parseFilter, Toposort, ToposortOptions } from '@nocobase/utils'; import EventEmitter from 'events'; -import parse from 'json-templates'; import compose from 'koa-compose'; import lodash from 'lodash'; import { ACLAvailableAction, AvailableActionOptions } from './acl-available-action'; @@ -135,7 +134,7 @@ export class ACL extends EventEmitter { if (params && resourcerAction.mergeParams) { const filteredParams = filterParams(ctx, resourceName, params); - const parsedParams = acl.parseJsonTemplate(filteredParams, ctx); + const parsedParams = await acl.parseJsonTemplate(filteredParams, ctx); ctx.permission.parsedParams = parsedParams; ctx.log?.info && ctx.log.info('acl parsedParams', parsedParams); @@ -332,12 +331,25 @@ export class ACL extends EventEmitter { } } - parseJsonTemplate(json: any, ctx: any) { - return parse(json)({ - ctx: { - state: JSON.parse(JSON.stringify(ctx.state)), - }, - }); + async parseJsonTemplate(json: any, ctx: any) { + if (json.filter) { + ctx.logger?.info?.('parseJsonTemplate.raw', JSON.parse(JSON.stringify(json.filter))); + const timezone = ctx?.get?.('x-timezone'); + const state = JSON.parse(JSON.stringify(ctx.state)); + const filter = await parseFilter(json.filter, { + timezone, + now: new Date().toISOString(), + vars: { + ctx: { + state, + }, + $user: async () => state.currentUser, + }, + }); + json.filter = filter; + ctx.logger?.info?.('parseJsonTemplate.parsed', filter); + } + return json; } middleware() { diff --git a/packages/core/client/src/acl/Configuration/FilterDynamicComponent.tsx b/packages/core/client/src/acl/Configuration/FilterDynamicComponent.tsx new file mode 100644 index 000000000..a88b857c5 --- /dev/null +++ b/packages/core/client/src/acl/Configuration/FilterDynamicComponent.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { useCompile } from '../../schema-component'; +import { useFilterOptions } from '../../schema-component/antd/filter'; +import { useValues } from '../../schema-component/antd/filter/useValues'; +import { Variable } from '../../schema-component/antd/variable'; + +interface GetOptionsParams { + schema: any; + operator: string; + maxDepth: number; + count?: number; +} + +const useOptions = (collectionName: string, { schema, operator, maxDepth, count = 1 }: GetOptionsParams) => { + if (count > maxDepth) { + return []; + } + + const result = useFilterOptions(collectionName).map((option) => { + if ((option.type !== 'belongsTo' && option.type !== 'hasOne') || !option.target) { + return { + key: option.name, + value: option.name, + label: option.title, + // TODO: 现在是通过组件的名称来过滤能够被选择的选项,这样的坏处是不够精确,后续可以优化 + disabled: schema?.['x-component'] !== option.schema['x-component'], + }; + } + + const children = + useOptions(option.target, { + schema, + operator, + maxDepth, + count: count + 1, + }) || []; + + return { + key: option.name, + value: option.name, + label: option.title, + children, + disabled: children.every((child) => child.disabled), + }; + }); + + return result; +}; + +const useUserVariable = ({ schema, operator }) => { + const options = useOptions('users', { schema, operator, maxDepth: 1 }) || []; + + return { + label: `{{t("Current user")}}`, + value: '$user', + key: '$user', + disabled: options.every((option) => option.disabled), + children: options, + }; +}; + +const useVariableOptions = () => { + const { operator, schema } = useValues(); + const userVariable = useUserVariable({ schema, operator }); + + if (!operator || !schema) return []; + + return [userVariable]; +}; + +export function FilterDynamicComponent(props) { + const { value, onChange, renderSchemaComponent } = props; + const options = useVariableOptions(); + const compile = useCompile(); + const scope = compile(options); + + return ( + + {renderSchemaComponent()} + + ); +} diff --git a/packages/core/client/src/acl/Configuration/schemas/scopes.ts b/packages/core/client/src/acl/Configuration/schemas/scopes.ts index cf8cbab32..dbeb28178 100644 --- a/packages/core/client/src/acl/Configuration/schemas/scopes.ts +++ b/packages/core/client/src/acl/Configuration/schemas/scopes.ts @@ -2,6 +2,7 @@ import { ISchema } from '@formily/react'; import { useContext, useEffect } from 'react'; import { useFormBlockContext } from '../../../block-provider'; import { useFilterOptions } from '../../../schema-component'; +import { FilterDynamicComponent } from '../FilterDynamicComponent'; import { RoleResourceCollectionContext } from '../RolesResourcesActions'; export const rolesResourcesScopesCollection = { @@ -139,6 +140,7 @@ export const scopesSchema: ISchema = { 'x-decorator': 'FormItem', 'x-component': 'Filter', 'x-component-props': { + dynamicComponent: FilterDynamicComponent, useProps() { const ctx = useContext(RoleResourceCollectionContext); const options = useFilterOptions(ctx.name); @@ -267,6 +269,7 @@ export const scopesSchema: ISchema = { 'x-decorator': 'FormItem', 'x-component': 'Filter', 'x-component-props': { + dynamicComponent: FilterDynamicComponent, useProps() { const ctx = useContext(RoleResourceCollectionContext); const options = useFilterOptions(ctx.name); diff --git a/packages/plugins/acl/src/server.ts b/packages/plugins/acl/src/server.ts index e535a2be1..15a694be4 100644 --- a/packages/plugins/acl/src/server.ts +++ b/packages/plugins/acl/src/server.ts @@ -537,7 +537,7 @@ export class PluginACL extends Plugin { ctx.permission.can = false; } } else { - const filter = parseJsonTemplate(action?.params?.filter || {}, ctx); + const filter = await parseJsonTemplate(action?.params?.filter || {}, ctx); const sourceInstance = await ctx.db.getRepository(collectionName).findOne({ filterByTk: resourceOf, filter,