diff --git a/packages/client/src/acl/Configuration/RolesResourcesActions.tsx b/packages/client/src/acl/Configuration/RolesResourcesActions.tsx
index 6016538b8..2fac6bd47 100644
--- a/packages/client/src/acl/Configuration/RolesResourcesActions.tsx
+++ b/packages/client/src/acl/Configuration/RolesResourcesActions.tsx
@@ -1,16 +1,19 @@
import { FormItem, FormLayout } from '@formily/antd';
import { ArrayField } from '@formily/core';
import { connect, useField, useForm } from '@formily/react';
-import { Checkbox, Select, Table, Tag } from 'antd';
+import { Checkbox, Table, Tag } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useAvailableActions } from '.';
import { useCollectionManager, useCompile, useRecord } from '../..';
+import { ScopeSelect } from './ScopeSelect';
const toActionMap = (arr: any[]) => {
const obj = {};
- arr?.forEach((action) => {
- obj[action.name] = action;
+ arr?.forEach?.((action) => {
+ if (action.name) {
+ obj[action.name] = action;
+ }
});
return obj;
};
@@ -52,11 +55,16 @@ export const RolesResourcesActions = connect((props) => {
}
onChange(Object.values(actionMap));
};
+ const setScope = (actionName, scope) => {
+ if (!actionMap[actionName]) {
+ toggleAction(actionName);
+ }
+ actionMap[actionName]['scope'] = scope;
+ };
const allChecked = {};
for (const action of availableActionsWithFields) {
allChecked[action.name] = collection?.fields?.length === actionMap?.[action.name]?.fields?.length;
}
-
return (
@@ -95,7 +103,15 @@ export const RolesResourcesActions = connect((props) => {
{
dataIndex: 'scope',
title: '可操作的数据范围',
- render: () => ,
+ render: (value, action) =>
+ !action.onNewRecord && (
+ {
+ setScope(action.name, scope);
+ }}
+ />
+ ),
},
]}
dataSource={availableActions?.map((item) => {
diff --git a/packages/client/src/acl/Configuration/ScopeSelect.tsx b/packages/client/src/acl/Configuration/ScopeSelect.tsx
new file mode 100644
index 000000000..bf46c3041
--- /dev/null
+++ b/packages/client/src/acl/Configuration/ScopeSelect.tsx
@@ -0,0 +1,46 @@
+import { createForm } from '@formily/core';
+import React, { createContext, useContext, useMemo, useState } from 'react';
+import { FormProvider, SchemaComponent } from '../../schema-component';
+import { scopesSchema } from './schemas/scopes';
+
+const RolesResourcesScopesSelectedRowKeysContext = createContext(null);
+
+const RolesResourcesScopesSelectedRowKeysProvider: React.FC = (props) => {
+ const [keys, setKeys] = useState([]);
+ return (
+
+ {props.children}
+
+ );
+};
+
+export const useRolesResourcesScopesSelectedRowKeys = () => {
+ return useContext(RolesResourcesScopesSelectedRowKeysContext);
+};
+
+export const ScopeSelect = (props) => {
+ const form = useMemo(
+ () =>
+ createForm({
+ values: {
+ scope: props.value,
+ },
+ }),
+ [],
+ );
+ console.log('props.value', props.value, form.values);
+ return (
+
+
+
+ );
+};
diff --git a/packages/client/src/acl/Configuration/schemas/scopes.ts b/packages/client/src/acl/Configuration/schemas/scopes.ts
new file mode 100644
index 000000000..e214d3769
--- /dev/null
+++ b/packages/client/src/acl/Configuration/schemas/scopes.ts
@@ -0,0 +1,202 @@
+import { ISchema, useForm } from '@formily/react';
+import { useActionContext } from '../../../';
+import { useAPIClient, useRequest } from '../../../api-client';
+import { useRecord } from '../../../record-provider';
+
+const collection = {
+ name: 'rolesResourcesScopes',
+ fields: [
+ {
+ type: 'string',
+ name: 'title',
+ interface: 'input',
+ uiSchema: {
+ title: '名称',
+ type: 'string',
+ 'x-component': 'Input',
+ required: true,
+ } as ISchema,
+ },
+ ],
+};
+
+export const scopesSchema: ISchema = {
+ type: 'object',
+ properties: {
+ scope: {
+ 'x-component': 'RecordPicker',
+ 'x-component-props': {
+ size: 'small',
+ fieldNames: {
+ label: 'name',
+ value: 'id',
+ },
+ onChange: '{{ onChange }}',
+ },
+ properties: {
+ options: {
+ 'x-decorator': 'RolesResourcesScopesSelectedRowKeysProvider',
+ 'x-component': 'RecordPicker.Options',
+ type: 'void',
+ title: '可操作的数据范围',
+ properties: {
+ actions: {
+ type: 'void',
+ 'x-component': 'ActionBar',
+ 'x-component-props': {
+ style: {
+ marginBottom: 16,
+ },
+ },
+ properties: {
+ // delete: {
+ // type: 'void',
+ // title: '删除',
+ // 'x-component': 'Action',
+ // },
+ create: {
+ type: 'void',
+ title: '添加数据范围',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ },
+ properties: {
+ drawer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer',
+ 'x-decorator': 'Form',
+ title: '添加数据范围',
+ properties: {
+ name: {
+ title: '数据范围名称',
+ 'x-component': 'Input',
+ 'x-decorator': 'FormItem',
+ },
+ // scope: {
+ // 'x-component': 'Input',
+ // 'x-decorator': 'FormItem',
+ // },
+ footer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer.Footer',
+ properties: {
+ action1: {
+ title: 'Cancel',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useCancelAction }}',
+ },
+ },
+ action2: {
+ title: 'Submit',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ useAction() {
+ const api = useAPIClient();
+ const ctx = useActionContext();
+ const form = useForm();
+ const record = useRecord();
+ return {
+ async run() {
+ await api.resource('rolesResourcesScopes').create({
+ values: {
+ ...form.values,
+ resourceName: record.name,
+ },
+ });
+ ctx.setVisible(false);
+ api.service('rolesResourcesScopesList')?.refresh?.();
+ },
+ };
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ input: {
+ type: 'array',
+ 'x-component': 'Table.RowSelection',
+ 'x-component-props': {
+ rowKey: 'id',
+ objectValue: true,
+ rowSelection: {
+ type: 'radio',
+ },
+ // useSelectedRowKeys() {
+ // const [selectedRowKeys, setSelectedRowKeys] = useRolesResourcesScopesSelectedRowKeys();
+ // return [selectedRowKeys, setSelectedRowKeys];
+ // },
+ useDataSource(options) {
+ const record = useRecord();
+ console.log('useRecord', record);
+ return useRequest(
+ {
+ resource: 'rolesResourcesScopes',
+ action: 'list',
+ params: {
+ sort: 'id',
+ filter: JSON.stringify({
+ $or: [
+ {
+ 'resourceName.$eq': record.name,
+ },
+ {
+ 'resourceName.$eq': '*',
+ },
+ ],
+ }),
+ },
+ },
+ {
+ ...options,
+ uid: 'rolesResourcesScopesList',
+ },
+ );
+ },
+ // dataSource: [
+ // { id: 1, name: 'Name1' },
+ // { id: 2, name: 'Name2' },
+ // { id: 3, name: 'Name3' },
+ // ],
+ },
+ properties: {
+ column1: {
+ type: 'void',
+ title: 'Name',
+ 'x-component': 'Table.Column',
+ properties: {
+ name: {
+ type: 'string',
+ 'x-component': 'Input',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ column2: {
+ type: 'void',
+ title: 'Actions',
+ 'x-component': 'Table.Column',
+ // properties: {
+ // delete: {
+ // type: 'void',
+ // title: '删除',
+ // 'x-component': 'Action.Link',
+ // },
+ // },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
diff --git a/packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx b/packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx
index d5c396faf..6abd0acd4 100644
--- a/packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx
+++ b/packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx
@@ -6,82 +6,97 @@ import {
FormContext,
mapProps,
mapReadPretty,
+ observer,
RecursionField,
- Schema,
useField,
- useFieldSchema
+ useFieldSchema,
+ useForm
} from '@formily/react';
import { toArr } from '@formily/shared';
import { Button, Drawer, Select, Space, Tag } from 'antd';
import React, { createContext, useContext, useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { useAttach } from '../../hooks/useAttach';
-import { ActionContext } from '../action';
+import { ActionContext, useActionContext } from '../action';
-const InputRecordPicker: React.FC = (props) => {
+const InputRecordPicker: React.FC = (props) => {
+ const { onChange } = props;
+ const fieldNames = { label: 'label', value: 'value', ...props.fieldNames };
const [visible, setVisible] = useState(false);
const fieldSchema = useFieldSchema();
const field = useField();
const s = fieldSchema.reduceProperties((buf, s) => {
- if (s['x-component'] === 'RowSelection') {
- return s;
+ if (s['x-component'] === 'RecordPicker.Options') {
+ return s.reduceProperties((buf, s) => {
+ if (s['x-component'] === 'Table.RowSelection') {
+ return s;
+ }
+ return buf;
+ }, null);
}
return buf;
- }, new Schema({}));
+ }, null);
+ const [value, setValue] = useState(field.value);
const form = useMemo(
() =>
createForm({
- initialValues: {
- [s.name]: field.value,
- },
+ initialValues: s?.name
+ ? {
+ [s.name]: field.value,
+ }
+ : {},
effects() {
onFormSubmit((form) => {
- field.value = form.values[s.name];
- console.log('field.value', form.values[s.name]);
+ setValue(form.values[s.name]);
+ onChange?.(form.values[s.name]);
});
},
}),
[],
);
+ const toValue = (value) => {
+ if (!value) {
+ return;
+ }
+ if (Array.isArray(value)) {
+ return value.map((item) => {
+ return {
+ label: item[fieldNames.label] || item[fieldNames.value],
+ value: item[fieldNames.value],
+ };
+ });
+ }
+ return {
+ label: value[fieldNames.label] || value[fieldNames.value],
+ value: value[fieldNames.value],
+ };
+ };
const f = useAttach(form.createVoidField({ ...field.props, basePath: '' }));
return (
- setVisible(false)}
- footer={
-
-
-
- }
- >
+
{
- // return s['x-component'] === 'RowSelection';
- // }}
+ filterProperties={(s) => {
+ return s['x-component'] === 'RecordPicker.Options';
+ }}
/>
-
+
@@ -125,6 +140,44 @@ export const RecordPicker: any = connect(
mapReadPretty(ReadPrettyRecordPicker),
);
+RecordPicker.Options = observer((props) => {
+ const field = useField();
+ const form = useForm();
+ const { visible, setVisible } = useActionContext();
+ const { t } = useTranslation();
+ return (
+ setVisible(false)}
+ footer={
+
+
+
+
+ }
+ />
+ );
+});
+
RecordPicker.SelectedItem = () => {
const ctx = useContext(RowContext);
const fieldSchema = useFieldSchema();
diff --git a/packages/client/src/schema-component/antd/record-picker/demos/demo1.tsx b/packages/client/src/schema-component/antd/record-picker/demos/demo1.tsx
index 60c44c25d..12d5c6389 100644
--- a/packages/client/src/schema-component/antd/record-picker/demos/demo1.tsx
+++ b/packages/client/src/schema-component/antd/record-picker/demos/demo1.tsx
@@ -3,7 +3,7 @@
*/
import { FormItem } from '@formily/antd';
import { ISchema } from '@formily/react';
-import { Action, Input, RecordPicker, RowSelection, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
+import { Action, Input, RecordPicker, SchemaComponent, SchemaComponentProvider, Table } from '@nocobase/client';
import React from 'react';
const schema: ISchema = {
@@ -18,6 +18,13 @@ const schema: ISchema = {
],
'x-decorator': 'FormItem',
'x-component': 'RecordPicker',
+ 'x-component-props': {
+ mode: 'tags',
+ fieldNames: {
+ label: 'name',
+ value: 'id',
+ },
+ },
'x-reactions': {
target: 'read',
fulfill: {
@@ -27,30 +34,39 @@ const schema: ISchema = {
},
},
properties: {
- rowSelection: {
- 'x-component': 'RowSelection',
- 'x-component-props': {
- rowKey: 'id',
- objectValue: true,
- rowSelection: {
- type: 'checkbox',
- },
- dataSource: [
- { id: 1, name: 'Name1' },
- { id: 2, name: 'Name2' },
- { id: 3, name: 'Name3' },
- ],
- },
+ options: {
+ 'x-component': 'RecordPicker.Options',
+ type: 'void',
+ title: 'Drawer Title',
properties: {
- column1: {
- type: 'void',
- title: 'Name',
- 'x-component': 'RowSelection.Column',
+ input: {
+ type: 'array',
+ title: `编辑模式`,
+ 'x-component': 'Table.RowSelection',
+ 'x-component-props': {
+ rowKey: 'id',
+ objectValue: true,
+ rowSelection: {
+ type: 'checkbox',
+ },
+ dataSource: [
+ { id: 1, name: 'Name1' },
+ { id: 2, name: 'Name2' },
+ { id: 3, name: 'Name3' },
+ ],
+ },
properties: {
- name: {
- type: 'string',
- 'x-component': 'Input',
- 'x-read-pretty': true,
+ column1: {
+ type: 'void',
+ title: 'Name',
+ 'x-component': 'Table.Column',
+ properties: {
+ name: {
+ type: 'string',
+ 'x-component': 'Input',
+ 'x-read-pretty': true,
+ },
+ },
},
},
},
@@ -102,7 +118,7 @@ const schema: ISchema = {
export default () => {
return (
-
+
);
diff --git a/packages/client/src/schema-component/core/FormProvider.tsx b/packages/client/src/schema-component/core/FormProvider.tsx
new file mode 100644
index 000000000..312890b4e
--- /dev/null
+++ b/packages/client/src/schema-component/core/FormProvider.tsx
@@ -0,0 +1,24 @@
+import {
+ FormProvider as FormilyFormProvider,
+ IProviderProps,
+ SchemaExpressionScopeContext,
+ SchemaOptionsContext
+} from '@formily/react';
+import React, { useContext } from 'react';
+import { SchemaComponentOptions } from './SchemaComponentOptions';
+
+export const FormProvider: React.FC = (props) => {
+ const { children, ...others } = props;
+ let options = useContext(SchemaOptionsContext);
+ const expressionScope = useContext(SchemaExpressionScopeContext);
+ const scope = { ...options?.scope, ...expressionScope };
+ const components = { ...options?.components };
+
+ return (
+
+
+ {children}
+
+
+ );
+};
diff --git a/packages/client/src/schema-component/core/index.tsx b/packages/client/src/schema-component/core/index.tsx
index 7f593d796..34a8623ea 100644
--- a/packages/client/src/schema-component/core/index.tsx
+++ b/packages/client/src/schema-component/core/index.tsx
@@ -1,5 +1,7 @@
export * from './DesignableSwitch';
+export * from './FormProvider';
export * from './RemoteSchemaComponent';
export * from './SchemaComponent';
export * from './SchemaComponentOptions';
export * from './SchemaComponentProvider';
+
diff --git a/packages/plugin-acl/src/server.ts b/packages/plugin-acl/src/server.ts
index cd7c00d56..dc013ef3e 100644
--- a/packages/plugin-acl/src/server.ts
+++ b/packages/plugin-acl/src/server.ts
@@ -238,7 +238,10 @@ export class PluginACL extends Plugin {
await this.writeRolesToACL();
});
- this.app.on('afterInstallUsersPlugin', async () => {
+ this.app.on('beforeInstallPlugin', async (plugin) => {
+ if (plugin.constructor.name !== 'UsersPlugin') {
+ return;
+ }
const repository = this.app.db.getRepository('roles');
await repository.createMany({
records: [
diff --git a/packages/plugin-users/src/server.ts b/packages/plugin-users/src/server.ts
index c526723cb..d1cc9191f 100644
--- a/packages/plugin-users/src/server.ts
+++ b/packages/plugin-users/src/server.ts
@@ -5,22 +5,6 @@ import * as actions from './actions/users';
import * as middlewares from './middlewares';
export default class UsersPlugin extends Plugin {
- async install() {
- const {
- adminNickname = 'Super Admin',
- adminEmail = 'admin@nocobase.com',
- adminPassword = 'admin123',
- } = this.options;
-
- const User = this.db.getCollection('users');
- await User.repository.create({
- values: {
- nickname: adminNickname,
- email: adminEmail,
- password: adminPassword,
- },
- });
- }
async beforeLoad() {
this.db.on('users.afterCreateWithAssociations', async (model, options) => {
@@ -83,4 +67,23 @@ export default class UsersPlugin extends Plugin {
directory: resolve(__dirname, 'collections'),
});
}
+
+ async install() {
+ const {
+ adminNickname = 'Super Admin',
+ adminEmail = 'admin@nocobase.com',
+ adminPassword = 'admin123',
+ } = this.options;
+
+ const User = this.db.getCollection('users');
+ await User.repository.create({
+ values: {
+ nickname: adminNickname,
+ email: adminEmail,
+ password: adminPassword,
+ roles: ['admin'],
+ },
+ });
+ }
+
}
diff --git a/packages/server/src/plugin-manager.ts b/packages/server/src/plugin-manager.ts
index cf78e3732..be8a1cf6b 100644
--- a/packages/server/src/plugin-manager.ts
+++ b/packages/server/src/plugin-manager.ts
@@ -1,6 +1,6 @@
+import { CleanOptions, SyncOptions } from '@nocobase/database';
import Application from './application';
import { Plugin } from './plugin';
-import { CleanOptions, SyncOptions } from '@nocobase/database';
interface PluginManagerOptions {
app: Application;