feat(acl): supports 'current role' variable and collections filtering (#3181)
* feat(acl): support current role variable * feat(acl): support to filter collections * fix: the save conditions button should not be shown * feat(formDataTemplates): supports to use variables for data scope * test: make testing more stable * fix: should only support filtering title and name * fix: fix parse * test: make testing more stable * fix: should not use async function
This commit is contained in:
parent
2f892f00d7
commit
aaee46e0c2
@ -265,6 +265,7 @@ export class ACL extends EventEmitter {
|
|||||||
state,
|
state,
|
||||||
},
|
},
|
||||||
$user: async () => state.currentUser,
|
$user: async () => state.currentUser,
|
||||||
|
$nRole: () => state.currentRole,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
json.filter = filter;
|
json.filter = filter;
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useCompile } from '../../schema-component';
|
|
||||||
import { useGetFilterOptions } 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;
|
|
||||||
getFilterOptions: (collectionName: string) => any[];
|
|
||||||
count?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOptions = (
|
|
||||||
collectionName: string,
|
|
||||||
{ schema, operator, maxDepth, getFilterOptions, count = 1 }: GetOptionsParams,
|
|
||||||
) => {
|
|
||||||
if (count > maxDepth) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = getFilterOptions(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 =
|
|
||||||
getOptions(option.target, {
|
|
||||||
schema,
|
|
||||||
operator,
|
|
||||||
maxDepth,
|
|
||||||
count: count + 1,
|
|
||||||
getFilterOptions,
|
|
||||||
}) || [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: option.name,
|
|
||||||
value: option.name,
|
|
||||||
label: option.title,
|
|
||||||
children,
|
|
||||||
disabled: children.every((child) => child.disabled),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useUserVariable = ({ schema, operator }) => {
|
|
||||||
const getFilterOptions = useGetFilterOptions();
|
|
||||||
const options = getOptions('users', { schema, operator, maxDepth: 1, getFilterOptions }) || [];
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Variable.Input value={value} onChange={onChange} scope={scope}>
|
|
||||||
{renderSchemaComponent()}
|
|
||||||
</Variable.Input>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
import { Spin } from 'antd';
|
import { Spin } from 'antd';
|
||||||
import React, { createContext, useContext } from 'react';
|
import React, { createContext, useContext } from 'react';
|
||||||
import { useRequest } from '../../api-client';
|
import { useRequest } from '../../api-client';
|
||||||
import { SchemaComponent } from '../../schema-component';
|
import { SchemaComponent, SchemaComponentContext } from '../../schema-component';
|
||||||
import { MenuItemsProvider } from '../Configuration/MenuItemsProvider';
|
import { MenuItemsProvider } from '../Configuration/MenuItemsProvider';
|
||||||
import { PermissionProvider, SettingCenterPermissionProvider } from '../Configuration/PermisionProvider';
|
import { PermissionProvider, SettingCenterPermissionProvider } from '../Configuration/PermisionProvider';
|
||||||
import { roleSchema } from './schemas/roles';
|
import { roleSchema } from './schemas/roles';
|
||||||
@ -28,12 +28,14 @@ export const useAvailableActions = () => {
|
|||||||
export const RoleTable = () => {
|
export const RoleTable = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<SchemaComponentContext.Provider value={{ designable: false }}>
|
||||||
<AvailableActionsProver>
|
<AvailableActionsProver>
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
schema={roleSchema}
|
schema={roleSchema}
|
||||||
components={{ MenuItemsProvider, SettingCenterPermissionProvider, PermissionProvider }}
|
components={{ MenuItemsProvider, SettingCenterPermissionProvider, PermissionProvider }}
|
||||||
/>
|
/>
|
||||||
</AvailableActionsProver>
|
</AvailableActionsProver>
|
||||||
|
</SchemaComponentContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
import { useRoleResourceValues } from './useRoleResourceValues';
|
import { useRoleResourceValues } from './useRoleResourceValues';
|
||||||
import { useSaveRoleResourceAction } from './useSaveRoleResourceAction';
|
import { useSaveRoleResourceAction } from './useSaveRoleResourceAction';
|
||||||
|
|
||||||
@ -90,6 +91,31 @@ export const roleCollectionsSchema: ISchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
filter: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Filter") }}',
|
||||||
|
default: {
|
||||||
|
$and: [{ title: { $includes: '' } }, { name: { $includes: '' } }],
|
||||||
|
},
|
||||||
|
'x-action': 'filter',
|
||||||
|
'x-component': 'Filter.Action',
|
||||||
|
'x-component-props': {
|
||||||
|
icon: 'FilterOutlined',
|
||||||
|
useProps: '{{ cm.useFilterActionProps }}',
|
||||||
|
},
|
||||||
|
'x-align': 'left',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
table1: {
|
table1: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-uid': 'input',
|
'x-uid': 'input',
|
||||||
|
@ -2,7 +2,7 @@ import { ISchema } from '@formily/react';
|
|||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { useFormBlockContext } from '../../../block-provider';
|
import { useFormBlockContext } from '../../../block-provider';
|
||||||
import { useFilterOptions } from '../../../schema-component';
|
import { useFilterOptions } from '../../../schema-component';
|
||||||
import { FilterDynamicComponent } from '../FilterDynamicComponent';
|
import { VariableInput } from '../../../schema-settings';
|
||||||
import { RoleResourceCollectionContext } from '../RolesResourcesActions';
|
import { RoleResourceCollectionContext } from '../RolesResourcesActions';
|
||||||
|
|
||||||
export const rolesResourcesScopesCollection = {
|
export const rolesResourcesScopesCollection = {
|
||||||
@ -140,11 +140,10 @@ export const scopesSchema: ISchema = {
|
|||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Filter',
|
'x-component': 'Filter',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
dynamicComponent: FilterDynamicComponent,
|
dynamicComponent: VariableInput,
|
||||||
useProps() {
|
useProps() {
|
||||||
const ctx = useContext(RoleResourceCollectionContext);
|
const ctx = useContext(RoleResourceCollectionContext);
|
||||||
const options = useFilterOptions(ctx.name);
|
const options = useFilterOptions(ctx.name);
|
||||||
console.log('ctx.name', ctx.name, options);
|
|
||||||
return {
|
return {
|
||||||
options,
|
options,
|
||||||
};
|
};
|
||||||
@ -269,7 +268,7 @@ export const scopesSchema: ISchema = {
|
|||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Filter',
|
'x-component': 'Filter',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
dynamicComponent: FilterDynamicComponent,
|
dynamicComponent: VariableInput,
|
||||||
useProps() {
|
useProps() {
|
||||||
const ctx = useContext(RoleResourceCollectionContext);
|
const ctx = useContext(RoleResourceCollectionContext);
|
||||||
const options = useFilterOptions(ctx.name);
|
const options = useFilterOptions(ctx.name);
|
||||||
|
@ -78,7 +78,7 @@ const AssociationResourceActionProvider = (props) => {
|
|||||||
);
|
);
|
||||||
const resource = api.resource(request.resource, resourceOf);
|
const resource = api.resource(request.resource, resourceOf);
|
||||||
return (
|
return (
|
||||||
<ResourceContext.Provider value={{ type: 'association', resource, association }}>
|
<ResourceContext.Provider value={{ type: 'association', resource, association, collection }}>
|
||||||
<ResourceActionContext.Provider value={{ ...service, defaultRequest: request, dragSort }}>
|
<ResourceActionContext.Provider value={{ ...service, defaultRequest: request, dragSort }}>
|
||||||
<CollectionProvider collection={collection}>{props.children}</CollectionProvider>
|
<CollectionProvider collection={collection}>{props.children}</CollectionProvider>
|
||||||
</ResourceActionContext.Provider>
|
</ResourceActionContext.Provider>
|
||||||
|
@ -579,7 +579,8 @@ export const useFilterActionProps = () => {
|
|||||||
const options = useFilterFieldOptions(collection.fields);
|
const options = useFilterFieldOptions(collection.fields);
|
||||||
const service = useResourceActionContext();
|
const service = useResourceActionContext();
|
||||||
return useFilterFieldProps({
|
return useFilterFieldProps({
|
||||||
options,
|
// 目前仅需要支持筛选 title 和 name,其它字段可能会报错。详见:https://nocobase.height.app/T-2745
|
||||||
|
options: options.filter((option) => ['title', 'name'].includes(option.name)),
|
||||||
params: service.state?.params?.[0] || service.params,
|
params: service.state?.params?.[0] || service.params,
|
||||||
service,
|
service,
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@ import { mergeFilter } from '../../block-provider';
|
|||||||
import { useCollectionManager } from '../../collection-manager';
|
import { useCollectionManager } from '../../collection-manager';
|
||||||
import { SchemaComponent, SchemaComponentContext, removeNullCondition } from '../../schema-component';
|
import { SchemaComponent, SchemaComponentContext, removeNullCondition } from '../../schema-component';
|
||||||
import { ITemplate } from '../../schema-component/antd/form-v2/Templates';
|
import { ITemplate } from '../../schema-component/antd/form-v2/Templates';
|
||||||
|
import { VariableInput } from '../VariableInput';
|
||||||
import { AsDefaultTemplate } from './components/AsDefaultTemplate';
|
import { AsDefaultTemplate } from './components/AsDefaultTemplate';
|
||||||
import { ArrayCollapse } from './components/DataTemplateTitle';
|
import { ArrayCollapse } from './components/DataTemplateTitle';
|
||||||
import { getSelectedIdFilter } from './components/Designer';
|
import { getSelectedIdFilter } from './components/Designer';
|
||||||
@ -143,6 +144,9 @@ export const FormDataTemplates = observer(
|
|||||||
title: '{{ t("Assign data scope for the template") }}',
|
title: '{{ t("Assign data scope for the template") }}',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Filter',
|
'x-component': 'Filter',
|
||||||
|
'x-component-props': {
|
||||||
|
dynamicComponent: VariableInput,
|
||||||
|
},
|
||||||
'x-decorator-props': {
|
'x-decorator-props': {
|
||||||
style: {
|
style: {
|
||||||
marginBottom: '0px',
|
marginBottom: '0px',
|
||||||
|
Loading…
Reference in New Issue
Block a user