diff --git a/packages/client/src/acl/ACLAction.tsx b/packages/client/src/acl/ACLAction.tsx
new file mode 100644
index 000000000..81fa48321
--- /dev/null
+++ b/packages/client/src/acl/ACLAction.tsx
@@ -0,0 +1,65 @@
+import { Menu } from 'antd';
+import React, { useState } from 'react';
+import { LockOutlined } from '@ant-design/icons';
+import { SchemaComponent, useActionVisible, VisibleContext } from '../schema-component';
+import { ISchema, useForm } from '@formily/react';
+
+const useCloseAction = () => {
+ const { setVisible } = useActionVisible();
+ const form = useForm();
+ return {
+ async run() {
+ setVisible(false);
+ form.submit((values) => {
+ console.log(values);
+ });
+ },
+ };
+};
+
+const schema: ISchema = {
+ type: 'object',
+ properties: {
+ drawer1: {
+ 'x-component': 'Action.Drawer',
+ type: 'void',
+ title: 'Drawer Title',
+ properties: {
+ hello1: {
+ 'x-content': 'Hello',
+ title: 'T1',
+ },
+ footer1: {
+ 'x-component': 'Action.Drawer.Footer',
+ type: 'void',
+ properties: {
+ close1: {
+ title: 'Close',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ useCloseAction }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
+
+export const ACLAction = () => {
+ const [visible, setVisible] = useState(false);
+ return (
+
+ {
+ setVisible(true);
+ }}
+ >
+
+
+
+
+ );
+};
diff --git a/packages/client/src/acl/index.tsx b/packages/client/src/acl/index.tsx
index 332e3cb46..c5f671c00 100644
--- a/packages/client/src/acl/index.tsx
+++ b/packages/client/src/acl/index.tsx
@@ -1,2 +1,3 @@
export * from './ACLProvider';
-export * from './RolePermissionManager';
\ No newline at end of file
+export * from './RolePermissionManager';
+export * from './ACLAction';
diff --git a/packages/client/src/application/demos/demo2/index.tsx b/packages/client/src/application/demos/demo2/index.tsx
index 4a31f8b42..da6d304b7 100644
--- a/packages/client/src/application/demos/demo2/index.tsx
+++ b/packages/client/src/application/demos/demo2/index.tsx
@@ -12,6 +12,7 @@ import {
AntdConfigProvider,
SchemaComponentProvider,
Menu,
+ Action,
} from '@nocobase/client';
import { I18nextProvider } from 'react-i18next';
import { Spin } from 'antd';
@@ -23,7 +24,7 @@ const providers = [
[APIClientProvider, { apiClient }],
[I18nextProvider, { i18n }],
[AntdConfigProvider, { remoteLocale: true }],
- [SchemaComponentProvider, { components: { Menu } }],
+ [SchemaComponentProvider, { components: { Menu, Action } }],
[RouteSwitchProvider, { components: { AuthLayout, AdminLayout } }],
];
diff --git a/packages/client/src/collection-manager/CollectionManagerAction.tsx b/packages/client/src/collection-manager/CollectionManagerAction.tsx
new file mode 100644
index 000000000..819be2203
--- /dev/null
+++ b/packages/client/src/collection-manager/CollectionManagerAction.tsx
@@ -0,0 +1,65 @@
+import { Menu } from 'antd';
+import React, { useState } from 'react';
+import { DatabaseOutlined } from '@ant-design/icons';
+import { SchemaComponent, useActionVisible, VisibleContext } from '../schema-component';
+import { ISchema, useForm } from '@formily/react';
+
+const useCloseAction = () => {
+ const { setVisible } = useActionVisible();
+ const form = useForm();
+ return {
+ async run() {
+ setVisible(false);
+ form.submit((values) => {
+ console.log(values);
+ });
+ },
+ };
+};
+
+const schema: ISchema = {
+ type: 'object',
+ properties: {
+ drawer1: {
+ 'x-component': 'Action.Drawer',
+ type: 'void',
+ title: 'Drawer Title',
+ properties: {
+ hello1: {
+ 'x-content': 'Hello',
+ title: 'T1',
+ },
+ footer1: {
+ 'x-component': 'Action.Drawer.Footer',
+ type: 'void',
+ properties: {
+ close1: {
+ title: 'Close',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ useCloseAction }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
+
+export const CollectionManagerAction = () => {
+ const [visible, setVisible] = useState(false);
+ return (
+
+ {
+ setVisible(true);
+ }}
+ >
+
+
+
+
+ );
+};
diff --git a/packages/client/src/collection-manager/index.tsx b/packages/client/src/collection-manager/index.tsx
index 9e8059b38..a0962f948 100644
--- a/packages/client/src/collection-manager/index.tsx
+++ b/packages/client/src/collection-manager/index.tsx
@@ -5,3 +5,4 @@ export * from './context';
export * from './hooks';
export * from './types';
export * from './CollectionField';
+export * from './CollectionManagerAction';
diff --git a/packages/client/src/current-user/ProfileAction.tsx b/packages/client/src/current-user/ProfileAction.tsx
new file mode 100644
index 000000000..cc850e212
--- /dev/null
+++ b/packages/client/src/current-user/ProfileAction.tsx
@@ -0,0 +1,65 @@
+import { Menu } from 'antd';
+import React, { useState } from 'react';
+import { DatabaseOutlined } from '@ant-design/icons';
+import { SchemaComponent, useActionVisible, VisibleContext } from '../schema-component';
+import { ISchema, useForm } from '@formily/react';
+
+const useCloseAction = () => {
+ const { setVisible } = useActionVisible();
+ const form = useForm();
+ return {
+ async run() {
+ setVisible(false);
+ form.submit((values) => {
+ console.log(values);
+ });
+ },
+ };
+};
+
+const schema: ISchema = {
+ type: 'object',
+ properties: {
+ drawer1: {
+ 'x-component': 'Action.Drawer',
+ type: 'void',
+ title: 'Drawer Title',
+ properties: {
+ hello1: {
+ 'x-content': 'Hello',
+ title: 'T1',
+ },
+ footer1: {
+ 'x-component': 'Action.Drawer.Footer',
+ type: 'void',
+ properties: {
+ close1: {
+ title: 'Close',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ useCloseAction }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
+
+export const ProfileAction = () => {
+ const [visible, setVisible] = useState(false);
+ return (
+
+ {
+ setVisible(true);
+ }}
+ >
+ 个人资料
+
+
+
+ );
+};
diff --git a/packages/client/src/current-user/index.tsx b/packages/client/src/current-user/index.tsx
index 6ceeda644..616b8fb13 100644
--- a/packages/client/src/current-user/index.tsx
+++ b/packages/client/src/current-user/index.tsx
@@ -1,3 +1,19 @@
-export function CurrentUser() {
-
-}
+import { Menu } from 'antd';
+import React from 'react';
+import { MoreOutlined, DesktopOutlined } from '@ant-design/icons';
+import { ProfileAction } from './ProfileAction';
+
+export const CurrentUser = () => null;
+
+CurrentUser.Dropdown = () => {
+ return (
+ // @ts-ignore
+
+
+ 切换角色
+ 语言设置
+
+ 注销
+
+ );
+};
diff --git a/packages/client/src/index.tsx b/packages/client/src/index.tsx
index a7be8354d..e85c14eb3 100644
--- a/packages/client/src/index.tsx
+++ b/packages/client/src/index.tsx
@@ -14,3 +14,4 @@ export * from './system-settings';
export * from './application';
export * from './record-provider';
export * from './async-data-provider';
+export * from './plugin-manager';
diff --git a/packages/client/src/plugin-manager/PluginManager.tsx b/packages/client/src/plugin-manager/PluginManager.tsx
new file mode 100644
index 000000000..56a119ee9
--- /dev/null
+++ b/packages/client/src/plugin-manager/PluginManager.tsx
@@ -0,0 +1,53 @@
+import React, { createContext } from 'react';
+import { Menu, Space } from 'antd';
+import { SettingOutlined, MoreOutlined, DesktopOutlined } from '@ant-design/icons';
+import { CurrentUser, DesignableSwitch, CollectionManagerAction, ACLAction, SystemSettings } from '../';
+import { get } from 'lodash';
+import { PluginManagerProvider } from '.';
+
+export const PluginManager = () => null;
+
+PluginManager.Provider = PluginManagerProvider;
+
+PluginManager.Toolbar = (props: any) => {
+ const components = { CurrentUser, DesignableSwitch, CollectionManagerAction, ACLAction, SystemSettings };
+ const items = [
+ {
+ action: 'DesignableSwitch',
+ pin: true,
+ },
+ {
+ action: 'CollectionManagerAction',
+ pin: true,
+ },
+ {
+ action: 'ACLAction',
+ pin: true,
+ },
+ {
+ action: 'SystemSettings.Action',
+ },
+ ];
+ const CurrentUserDropdown = get(components, 'CurrentUser.Dropdown');
+ return (
+
+ );
+};
diff --git a/packages/client/src/plugin-manager/PluginManagerProvider.tsx b/packages/client/src/plugin-manager/PluginManagerProvider.tsx
new file mode 100644
index 000000000..35a28f5b2
--- /dev/null
+++ b/packages/client/src/plugin-manager/PluginManagerProvider.tsx
@@ -0,0 +1,7 @@
+import React from 'react';
+import { PluginManagerContext } from './context';
+
+export const PluginManagerProvider: React.FC = (props) => {
+ const { components, children } = props;
+ return {children};
+};
diff --git a/packages/client/src/plugin-manager/context.ts b/packages/client/src/plugin-manager/context.ts
new file mode 100644
index 000000000..3220782c6
--- /dev/null
+++ b/packages/client/src/plugin-manager/context.ts
@@ -0,0 +1,3 @@
+import { createContext } from 'react';
+
+export const PluginManagerContext = createContext({});
diff --git a/packages/client/src/plugin-manager/index.md b/packages/client/src/plugin-manager/index.md
new file mode 100644
index 000000000..fd2687e1b
--- /dev/null
+++ b/packages/client/src/plugin-manager/index.md
@@ -0,0 +1,55 @@
+---
+nav:
+ path: /client
+group:
+ path: /client
+---
+
+# PluginManager
+
+```tsx | pure
+
+
+
+```
+
+## 扩展
+
+如何扩展插件的 `PluginManager.Toolbar.Item`
+
+```tsx | pure
+import React from 'react';
+import { Button, Menu } from 'antd';
+import { HighlightOutlined } from '@ant-design/icons';
+
+export const DesignableSwitch = () => {
+ const { designable, setDesignable } = useDesignable();
+ return (
+
+
+
+ );
+};
+```
\ No newline at end of file
diff --git a/packages/client/src/plugin-manager/index.ts b/packages/client/src/plugin-manager/index.ts
new file mode 100644
index 000000000..dfbf7596f
--- /dev/null
+++ b/packages/client/src/plugin-manager/index.ts
@@ -0,0 +1,3 @@
+export * from './context';
+export * from './PluginManager';
+export * from './PluginManagerProvider';
diff --git a/packages/client/src/route-switch/antd/admin-layout/index.md b/packages/client/src/route-switch/antd/admin-layout/index.md
index b98d998ec..229736389 100644
--- a/packages/client/src/route-switch/antd/admin-layout/index.md
+++ b/packages/client/src/route-switch/antd/admin-layout/index.md
@@ -14,12 +14,12 @@ group:
- Layout.Header
- SiteTitle
- Menu(SchemaComponent)
- - PluginActionBar
- - Designable.Action(引用)
- - CollectionManager.Action(引用)
- - ACL.Action(引用)
- - SystemSettings.Action(引用)
- - CurrentUser.Dropdown(引用)
+ - PluginManager.Toolbar
+ - DesignableAction
+ - CollectionManager.Action
+ - ACL.Action
+ - SystemSettings.Action
+ - CurrentUser.Dropdown
- Layout.Sider
- SideMenu(随 Menu 联动的)
- Layout.Content
diff --git a/packages/client/src/route-switch/antd/admin-layout/index.tsx b/packages/client/src/route-switch/antd/admin-layout/index.tsx
index b8cc84c96..ff5601eaa 100644
--- a/packages/client/src/route-switch/antd/admin-layout/index.tsx
+++ b/packages/client/src/route-switch/antd/admin-layout/index.tsx
@@ -1,8 +1,8 @@
import React, { useRef, useState } from 'react';
-import { Layout, Spin } from 'antd';
-import { useRoute } from '../..';
-import { findMenuItem, RemoteSchemaComponent } from '../../../schema-component';
-import { Redirect, useHistory, useRouteMatch } from 'react-router-dom';
+import { Layout } from 'antd';
+import { useRoute } from '../../hooks';
+import { useHistory, useRouteMatch } from 'react-router-dom';
+import { findMenuItem, RemoteSchemaComponent, PluginManager } from '../../../';
export function AdminLayout(props: any) {
const route = useRoute();
@@ -19,28 +19,33 @@ export function AdminLayout(props: any) {
const [hidden, setHidden] = useState(false);
return (
-
- {
- data['x-component-props']['defaultSelectedUid'] = defaultSelectedUid;
- return data;
- }}
- onSuccess={(data) => {
- if (defaultSelectedUid) {
- return;
- }
- setHidden(true);
- setTimeout(() => setHidden(false), 11);
- const s = findMenuItem(data?.data);
- if (s) {
- setSchema(s);
- history.push(`/admin/${s['x-uid']}`);
- }
- }}
- />
+
+
+ {
+ data['x-component-props']['defaultSelectedUid'] = defaultSelectedUid;
+ return data;
+ }}
+ onSuccess={(data) => {
+ if (defaultSelectedUid) {
+ return;
+ }
+ setHidden(true);
+ setTimeout(() => setHidden(false), 11);
+ const s = findMenuItem(data?.data);
+ if (s) {
+ setSchema(s);
+ history.push(`/admin/${s['x-uid']}`);
+ }
+ }}
+ />
+
+
diff --git a/packages/client/src/schema-component/antd/action/demos/demo2.tsx b/packages/client/src/schema-component/antd/action/demos/demo2.tsx
new file mode 100644
index 000000000..249c267c9
--- /dev/null
+++ b/packages/client/src/schema-component/antd/action/demos/demo2.tsx
@@ -0,0 +1,67 @@
+import React, { useState } from 'react';
+import { observer, ISchema, useForm } from '@formily/react';
+import {
+ SchemaComponent,
+ SchemaComponentProvider,
+ Form,
+ Action,
+ useActionVisible,
+ VisibleContext,
+} from '@nocobase/client';
+import { FormItem, Input } from '@formily/antd';
+import { Button } from 'antd';
+
+const useCloseAction = () => {
+ const { setVisible } = useActionVisible();
+ const form = useForm();
+ return {
+ async run() {
+ setVisible(false);
+ form.submit((values) => {
+ console.log(values);
+ });
+ },
+ };
+};
+
+const schema: ISchema = {
+ type: 'object',
+ properties: {
+ drawer1: {
+ 'x-component': 'Action.Drawer',
+ type: 'void',
+ title: 'Drawer Title',
+ properties: {
+ hello1: {
+ 'x-content': 'Hello',
+ title: 'T1',
+ },
+ footer1: {
+ 'x-component': 'Action.Drawer.Footer',
+ type: 'void',
+ properties: {
+ close1: {
+ title: 'Close',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ useCloseAction }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
+
+export default observer(() => {
+ const [visible, setVisible] = useState(false);
+ return (
+
+
+ setVisible(true)}>Open
+
+
+
+ );
+});
diff --git a/packages/client/src/schema-component/antd/action/index.md b/packages/client/src/schema-component/antd/action/index.md
index 449647801..7189d1483 100644
--- a/packages/client/src/schema-component/antd/action/index.md
+++ b/packages/client/src/schema-component/antd/action/index.md
@@ -30,4 +30,12 @@ group:
## Examples
+### Action + Action.Drawer
+
+
+### VisibleContext + Action.Drawer
+
+只配置 Action.Drawer,而不需要 Action,通过 VisibleContext 自定义按钮。
+
+
diff --git a/packages/client/src/schema-component/components/DesignableSwitch.tsx b/packages/client/src/schema-component/components/DesignableSwitch.tsx
new file mode 100644
index 000000000..6ca36d53c
--- /dev/null
+++ b/packages/client/src/schema-component/components/DesignableSwitch.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import { Button, Menu } from 'antd';
+import { HighlightOutlined } from '@ant-design/icons';
+import cls from 'classnames';
+import { useDesignable } from '..';
+
+export const DesignableSwitch = () => {
+ const { designable, setDesignable } = useDesignable();
+ return (
+
+
+
+ );
+};
diff --git a/packages/client/src/schema-component/components/index.tsx b/packages/client/src/schema-component/components/index.tsx
index 6afcac991..049cb67c2 100644
--- a/packages/client/src/schema-component/components/index.tsx
+++ b/packages/client/src/schema-component/components/index.tsx
@@ -3,3 +3,4 @@ export * from './SchemaComponent';
export * from './SchemaComponentProvider';
export * from './SchemaOptionsExpressionScopeProvider';
export * from './RemoteSchemaComponent';
+export * from './DesignableSwitch';
diff --git a/packages/client/src/schema-component/hooks/useDesignable.tsx b/packages/client/src/schema-component/hooks/useDesignable.tsx
index 8064d1689..0e78df8ad 100644
--- a/packages/client/src/schema-component/hooks/useDesignable.tsx
+++ b/packages/client/src/schema-component/hooks/useDesignable.tsx
@@ -216,7 +216,7 @@ export class Designable {
// TODO
export function useDesignable() {
- const { designable, refresh, reset } = useContext(SchemaComponentContext);
+ const { designable, setDesignable, refresh, reset } = useContext(SchemaComponentContext);
const DesignableBar = () => {
return <>>;
};
@@ -229,6 +229,7 @@ export function useDesignable() {
designable,
reset,
refresh,
+ setDesignable,
DesignableBar,
on: dn.on.bind(dn),
// TODO
diff --git a/packages/client/src/system-settings/SystemSettingsAction.tsx b/packages/client/src/system-settings/SystemSettingsAction.tsx
new file mode 100644
index 000000000..5acbb4dad
--- /dev/null
+++ b/packages/client/src/system-settings/SystemSettingsAction.tsx
@@ -0,0 +1,65 @@
+import { Menu } from 'antd';
+import React, { useState } from 'react';
+import { LockOutlined } from '@ant-design/icons';
+import { SchemaComponent, useActionVisible, VisibleContext } from '../schema-component';
+import { ISchema, useForm } from '@formily/react';
+
+const useCloseAction = () => {
+ const { setVisible } = useActionVisible();
+ const form = useForm();
+ return {
+ async run() {
+ setVisible(false);
+ form.submit((values) => {
+ console.log(values);
+ });
+ },
+ };
+};
+
+const schema: ISchema = {
+ type: 'object',
+ properties: {
+ drawer1: {
+ 'x-component': 'Action.Drawer',
+ type: 'void',
+ title: 'Drawer Title',
+ properties: {
+ hello1: {
+ 'x-content': 'Hello',
+ title: 'T1',
+ },
+ footer1: {
+ 'x-component': 'Action.Drawer.Footer',
+ type: 'void',
+ properties: {
+ close1: {
+ title: 'Close',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ useCloseAction }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
+
+export const SystemSettingsAction = () => {
+ const [visible, setVisible] = useState(false);
+ return (
+
+ {
+ setVisible(true);
+ }}
+ >
+ 系统设置
+
+
+
+ );
+};
diff --git a/packages/client/src/system-settings/index.tsx b/packages/client/src/system-settings/index.tsx
index d398413d3..645bbd698 100644
--- a/packages/client/src/system-settings/index.tsx
+++ b/packages/client/src/system-settings/index.tsx
@@ -1,3 +1,5 @@
-export function SystemSettings() {
-
-}
\ No newline at end of file
+import { SystemSettingsAction } from './SystemSettingsAction';
+
+export const SystemSettings = () => null;
+
+SystemSettings.Action = SystemSettingsAction;