diff --git a/packages/client/src/application/demos/demo2/index.tsx b/packages/client/src/application/demos/demo2/index.tsx index 6f534d044..2ecb92437 100644 --- a/packages/client/src/application/demos/demo2/index.tsx +++ b/packages/client/src/application/demos/demo2/index.tsx @@ -1,6 +1,5 @@ import { ACLShortcut, - Action, AdminLayout, AntdConfigProvider, AntdSchemaComponentProvider, @@ -11,12 +10,13 @@ import { DesignableSwitch, DocumentTitleProvider, i18n, - Menu, - Page, PluginManagerProvider, + RouteSchemaComponent, RouteSwitch, RouteSwitchProvider, SchemaComponentProvider, + SigninPage, + SignupPage, SystemSettingsProvider, SystemSettingsShortcut, useRequest @@ -24,7 +24,7 @@ import { import { Spin } from 'antd'; import React from 'react'; import { I18nextProvider } from 'react-i18next'; -import { MemoryRouter } from 'react-router-dom'; +import { Link, MemoryRouter, NavLink } from 'react-router-dom'; import apiClient from './apiClient'; const providers = [ @@ -38,10 +38,10 @@ const providers = [ PluginManagerProvider, { components: { ACLShortcut, DesignableSwitch, CollectionManagerShortcut, SystemSettingsShortcut } }, ], - [SchemaComponentProvider, { components: { Page, Menu, Action } }], + [SchemaComponentProvider, { components: { Link, NavLink } }], AntdSchemaComponentProvider, [DocumentTitleProvider, { addonAfter: 'NocoBase' }], - [RouteSwitchProvider, { components: { AuthLayout, AdminLayout } }], + [RouteSwitchProvider, { components: { AuthLayout, AdminLayout, RouteSchemaComponent, SigninPage, SignupPage } }], ]; const App = compose(...providers)(() => { diff --git a/packages/client/src/application/demos/demo2/mock.ts b/packages/client/src/application/demos/demo2/mock.ts index 02ed7ba74..145ba8482 100644 --- a/packages/client/src/application/demos/demo2/mock.ts +++ b/packages/client/src/application/demos/demo2/mock.ts @@ -145,10 +145,13 @@ export default (apiClient: APIClient) => { routes: [ { type: 'route', - uiSchemaUid: 'dtf9j0b8p9u', path: '/signin', - component: 'RouteSchemaComponent', - title: '{{t("Sign in")}}', + component: 'SigninPage', + }, + { + type: 'route', + path: '/signup', + component: 'SignupPage', }, ], }, diff --git a/packages/client/src/current-user/ProfileAction.tsx b/packages/client/src/current-user/ProfileAction.tsx deleted file mode 100644 index cc850e212..000000000 --- a/packages/client/src/current-user/ProfileAction.tsx +++ /dev/null @@ -1,65 +0,0 @@ -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.md b/packages/client/src/current-user/index.md deleted file mode 100644 index b5cc5a50c..000000000 --- a/packages/client/src/current-user/index.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -nav: - path: /client -group: - title: Client - path: /client ---- - -# CurrentUser 待定 diff --git a/packages/client/src/document-title/index.tsx b/packages/client/src/document-title/index.tsx index bf68d5fb4..00d3afc67 100644 --- a/packages/client/src/document-title/index.tsx +++ b/packages/client/src/document-title/index.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useState } from 'react'; +import React, { createContext, useContext, useEffect, useState } from 'react'; import { Helmet } from 'react-helmet'; interface DocumentTitleContextProps { @@ -35,3 +35,10 @@ export const DocumentTitleProvider: React.FC<{ addonBefore?: string; addonAfter? export const useDocumentTitle = () => { return useContext(DocumentTitleContext); }; + +export const useCurrentDocumentTitle = (title: string) => { + const { setTitle } = useDocumentTitle(); + useEffect(() => { + setTitle(title); + }, []); +}; diff --git a/packages/client/src/index.tsx b/packages/client/src/index.tsx index 179c1f91c..69ab92cf9 100644 --- a/packages/client/src/index.tsx +++ b/packages/client/src/index.tsx @@ -8,7 +8,6 @@ export * from './application'; export * from './async-data-provider'; export * from './board'; export * from './collection-manager'; -export * from './current-user'; export * from './document-title'; export * from './i18n'; export * from './icon'; @@ -17,4 +16,5 @@ export * from './record-provider'; export * from './route-switch'; export * from './schema-component'; export * from './system-settings'; +export * from './user'; diff --git a/packages/client/src/plugin-manager/PluginManager.tsx b/packages/client/src/plugin-manager/PluginManager.tsx index f67c557a7..86c177d78 100644 --- a/packages/client/src/plugin-manager/PluginManager.tsx +++ b/packages/client/src/plugin-manager/PluginManager.tsx @@ -34,28 +34,39 @@ interface ToolbarItemProps { pin?: boolean; } +const splitItems = (items: ToolbarItemProps[]) => { + const pinned = []; + const unpinned = []; + for (const item of items) { + if (item.pin) { + pinned.push(item); + } else { + unpinned.push(item); + } + } + return [pinned, unpinned]; +}; + PluginManager.Toolbar = (props: ToolbarProps) => { const { components } = useContext(PluginManagerContext); const { items = [] } = props; + const [pinned, unpinned] = splitItems(items); return (
- {items - .filter((item) => item.pin) - .map((item, index) => { - const Action = get(components, item.component); - return ( - Action && ( - - - - ) - ); - })} - }> - {items - .filter((item) => !item.pin) - .map((item, index) => { + {pinned.map((item, index) => { + const Action = get(components, item.component); + return ( + Action && ( + + + + ) + ); + })} + {unpinned.length > 0 && ( + }> + {unpinned.map((item, index) => { const Action = get(components, item.component); return ( Action && ( @@ -65,11 +76,12 @@ PluginManager.Toolbar = (props: ToolbarProps) => { ) ); })} - - }> - 管理插件 - - + {unpinned.length > 0 && } + }> + 管理插件 + + + )}
); 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 fd2aa2eaa..a32b3b194 100644 --- a/packages/client/src/route-switch/antd/admin-layout/index.tsx +++ b/packages/client/src/route-switch/antd/admin-layout/index.tsx @@ -7,9 +7,9 @@ import { PluginManager, RemoteSchemaComponent, useDocumentTitle, + useRoute, useSystemSettings } from '../../../'; -import { useRoute } from '../../hooks'; export function AdminLayout(props: any) { const route = useRoute(); @@ -64,7 +64,7 @@ export function AdminLayout(props: any) { { component: 'SystemSettingsShortcut' }, ]} /> - + diff --git a/packages/client/src/route-switch/antd/auth-layout/index.tsx b/packages/client/src/route-switch/antd/auth-layout/index.tsx index ce3a24797..1f77ce0dd 100644 --- a/packages/client/src/route-switch/antd/auth-layout/index.tsx +++ b/packages/client/src/route-switch/antd/auth-layout/index.tsx @@ -2,7 +2,13 @@ import React from 'react'; export function AuthLayout(props: any) { return ( -
+

NocoBase

{props.children}
diff --git a/packages/client/src/route-switch/antd/index.ts b/packages/client/src/route-switch/antd/index.ts index 1e732acc5..8c8df6eb7 100644 --- a/packages/client/src/route-switch/antd/index.ts +++ b/packages/client/src/route-switch/antd/index.ts @@ -1,2 +1,4 @@ export * from './admin-layout'; export * from './auth-layout'; +export * from './route-schema-component'; + diff --git a/packages/client/src/route-switch/antd/route-schema-component/index.tsx b/packages/client/src/route-switch/antd/route-schema-component/index.tsx new file mode 100644 index 000000000..50e591fc4 --- /dev/null +++ b/packages/client/src/route-switch/antd/route-schema-component/index.tsx @@ -0,0 +1,7 @@ +import React from 'react'; +import { RemoteSchemaComponent, useRoute } from '../../../'; + +export function RouteSchemaComponent(props: any) { + const route = useRoute(); + return ; +} diff --git a/packages/client/src/schema-component/antd/action/Action.tsx b/packages/client/src/schema-component/antd/action/Action.tsx index a04d80ecf..180678f05 100644 --- a/packages/client/src/schema-component/antd/action/Action.tsx +++ b/packages/client/src/schema-component/antd/action/Action.tsx @@ -1,10 +1,10 @@ -import React, { useState } from 'react'; -import { Button, ButtonProps } from 'antd'; import { observer, RecursionField, useField, useFieldSchema } from '@formily/react'; -import { useA } from './hooks'; -import { VisibleContext } from './context'; -import { ComposedAction } from './types'; +import { Button } from 'antd'; +import React, { useState } from 'react'; import { ActionDrawer } from './Action.Drawer'; +import { VisibleContext } from './context'; +import { useA } from './hooks'; +import { ComposedAction } from './types'; export const Action: ComposedAction = observer((props) => { const { useAction = useA, onClick, ...others } = props; @@ -22,7 +22,7 @@ export const Action: ComposedAction = observer((props) => { run(); }} > - {schema.title} + {field.title} diff --git a/packages/client/src/system-settings/demos/demo1.tsx b/packages/client/src/system-settings/demos/demo1.tsx new file mode 100644 index 000000000..5fb03052d --- /dev/null +++ b/packages/client/src/system-settings/demos/demo1.tsx @@ -0,0 +1,55 @@ +import { + AntdSchemaComponentProvider, + APIClient, + APIClientProvider, + PluginManager, + PluginManagerProvider, + SchemaComponentProvider, + SystemSettingsProvider, + SystemSettingsShortcut, + useSystemSettings +} from '@nocobase/client'; +import MockAdapter from 'axios-mock-adapter'; +import React from 'react'; + +const apiClient = new APIClient(); + +const mock = new MockAdapter(apiClient.axios); + +mock.onGet('/system_settings:get').reply(200, { + data: { + title: 'NocoBase', + }, +}); + +const Demo = () => { + const { data } = useSystemSettings(); + return
+

以下为测试内容

+ System title: {data?.data?.title} +
; +}; + +export default () => { + return ( + + + + + + + + + + + + + ); +}; diff --git a/packages/client/src/system-settings/index.md b/packages/client/src/system-settings/index.md index eccd0bf7b..1978f7af1 100644 --- a/packages/client/src/system-settings/index.md +++ b/packages/client/src/system-settings/index.md @@ -5,11 +5,18 @@ group: path: /client --- -# SystemSettings 待定 +# SystemSettings ## SystemSettingsProvider -## SystemSettingsAction +## SystemSettingsShortcut ## useSystemSettings +```ts +const { data, loading, refresh } = useSystemSettings(); +``` + +## Examples + + diff --git a/packages/client/src/current-user/index.tsx b/packages/client/src/user/CurrentUser.tsx similarity index 64% rename from packages/client/src/current-user/index.tsx rename to packages/client/src/user/CurrentUser.tsx index 103358d05..7f36a358d 100644 --- a/packages/client/src/current-user/index.tsx +++ b/packages/client/src/user/CurrentUser.tsx @@ -1,11 +1,10 @@ import { Menu } from 'antd'; import React from 'react'; -import { MoreOutlined, DesktopOutlined } from '@ant-design/icons'; +import { useHistory } from 'react-router-dom'; import { ProfileAction } from './ProfileAction'; -export const CurrentUser = () => null; - -CurrentUser.Dropdown = () => { +export const CurrentUser = () => { + const history = useHistory(); return (
@@ -14,7 +13,13 @@ CurrentUser.Dropdown = () => { 切换角色 语言设置 - 注销 + { + history.push('/signin'); + }} + > + 注销 +
diff --git a/packages/client/src/user/ProfileAction.tsx b/packages/client/src/user/ProfileAction.tsx new file mode 100644 index 000000000..dd7f6c559 --- /dev/null +++ b/packages/client/src/user/ProfileAction.tsx @@ -0,0 +1,105 @@ +import { ISchema, useForm } from '@formily/react'; +import { uid } from '@formily/shared'; +import { Menu } from 'antd'; +import React, { useState } from 'react'; +import { SchemaComponent, useActionVisible, useRequest, VisibleContext } from '../'; + +const useCloseAction = () => { + const { setVisible } = useActionVisible(); + const form = useForm(); + return { + async run() { + setVisible(false); + form.submit((values) => { + console.log(values); + }); + }, + }; +}; + +const useCurrentUserValues = (props, options) => { + return useRequest( + () => + Promise.resolve({ + data: {}, + }), + { + ...options, + }, + ); +}; + +const useSaveCurrentUserValues = () => { + const { setVisible } = useActionVisible(); + const form = useForm(); + return { + async run() { + form.submit((values) => { + setVisible(false); + console.log(values); + }); + }, + }; +}; + +const schema: ISchema = { + type: 'object', + properties: { + [uid()]: { + 'x-decorator': 'Form', + 'x-decorator-props': { + useValues: '{{ useCurrentUserValues }}', + }, + 'x-component': 'Action.Drawer', + type: 'void', + title: '个人资料', + properties: { + nickname: { + type: 'string', + title: "{{t('Nickname')}}", + 'x-decorator': 'FormItem', + 'x-component': 'Input', + required: true, + }, + footer: { + 'x-component': 'Action.Drawer.Footer', + type: 'void', + properties: { + cancel: { + title: 'Cancel', + 'x-component': 'Action', + 'x-component-props': { + useAction: '{{ useCloseAction }}', + }, + }, + submit: { + title: 'Submit', + 'x-component': 'Action', + 'x-component-props': { + type: 'primary', + useAction: '{{ useSaveCurrentUserValues }}', + }, + }, + }, + }, + }, + }, + }, +}; + +export const ProfileAction = () => { + const [visible, setVisible] = useState(false); + return ( + + { + setVisible(true); + }} + > + 个人资料 + + + + ); +}; diff --git a/packages/client/src/user/SigninPage.tsx b/packages/client/src/user/SigninPage.tsx new file mode 100644 index 000000000..10704efd6 --- /dev/null +++ b/packages/client/src/user/SigninPage.tsx @@ -0,0 +1,79 @@ +import { ISchema, useForm } from '@formily/react'; +import { uid } from '@formily/shared'; +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { SchemaComponent, useCurrentDocumentTitle } from '..'; + +const schema: ISchema = { + type: 'object', + name: uid(), + 'x-component': 'Form', + properties: { + email: { + type: 'string', + required: true, + 'x-component': 'Input', + 'x-validator': 'email', + 'x-decorator': 'FormItem', + 'x-component-props': { placeholder: '{{t("Email")}}', style: {} }, + }, + password: { + type: 'string', + 'x-component': 'Password', + required: true, + 'x-decorator': 'FormItem', + 'x-component-props': { placeholder: '{{t("Password")}}', style: {} }, + }, + actions: { + type: 'void', + 'x-component': 'div', + properties: { + submit: { + title: '{{t("Sign in")}}', + type: 'void', + 'x-component': 'Action', + 'x-component-props': { + block: true, + type: 'primary', + useAction: '{{ useSignin }}', + style: { width: '100%' }, + }, + }, + }, + }, + link: { + type: 'void', + 'x-component': 'div', + properties: { + link: { + title: '{{t("Create an account")}}', + type: 'void', + 'x-component': 'Link', + 'x-content': '{{t("Create an account")}}', + 'x-component-props': { to: '/signup' }, + }, + }, + }, + }, +}; + +const useSignin = () => { + const history = useHistory(); + const form = useForm(); + return { + async run() { + await form.submit(); + console.log(form.values); + history.push('/admin'); + }, + }; +}; + +export const SigninPage = () => { + useCurrentDocumentTitle('Signin'); + return ( +
+ +
+ ); +}; diff --git a/packages/client/src/user/SignupPage.tsx b/packages/client/src/user/SignupPage.tsx new file mode 100644 index 000000000..68a5188a1 --- /dev/null +++ b/packages/client/src/user/SignupPage.tsx @@ -0,0 +1,101 @@ +import { ISchema, useForm } from '@formily/react'; +import { uid } from '@formily/shared'; +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { SchemaComponent, useCurrentDocumentTitle } from '..'; + +const schema: ISchema = { + type: 'object', + name: uid(), + 'x-component': 'Form', + properties: { + email: { + type: 'string', + required: true, + 'x-component': 'Input', + 'x-validator': 'email', + 'x-decorator': 'FormItem', + 'x-component-props': { placeholder: '{{t("Email")}}', style: {} }, + }, + password: { + type: 'string', + required: true, + 'x-component': 'Password', + 'x-decorator': 'FormItem', + 'x-component-props': { placeholder: '{{t("Password")}}', checkStrength: true, style: {} }, + 'x-reactions': [ + { + dependencies: ['.confirm_password'], + fulfill: { + state: { + selfErrors: '{{$deps[0] && $self.value && $self.value !== $deps[0] ? t("Password mismatch") : ""}}', + }, + }, + }, + ], + }, + confirm_password: { + type: 'string', + required: true, + 'x-component': 'Password', + 'x-decorator': 'FormItem', + 'x-component-props': { placeholder: '{{t("Confirm password")}}', checkStrength: true, style: {} }, + 'x-reactions': [ + { + dependencies: ['.password'], + fulfill: { + state: { + selfErrors: '{{$deps[0] && $self.value && $self.value !== $deps[0] ? t("Password mismatch") : ""}}', + }, + }, + }, + ], + }, + actions: { + type: 'void', + 'x-component': 'div', + properties: { + submit: { + title: '{{t("Sign up")}}', + type: 'void', + 'x-component': 'Action', + 'x-component-props': { + block: true, + type: 'primary', + useAction: '{{ useSignup }}', + style: { width: '100%' }, + }, + }, + }, + }, + link: { + type: 'void', + 'x-component': 'div', + properties: { + link: { + type: 'void', + 'x-component': 'Link', + 'x-component-props': { to: '/signin' }, + 'x-content': '{{t("Log in with an existing account")}}', + }, + }, + }, + }, +}; + +const useSignup = () => { + const history = useHistory(); + const form = useForm(); + return { + async run() { + await form.submit(); + console.log(form.values); + // history.push('/signin'); + }, + }; +}; + +export const SignupPage = () => { + useCurrentDocumentTitle('Signup'); + return ; +}; diff --git a/packages/client/src/user/index.md b/packages/client/src/user/index.md new file mode 100644 index 000000000..09337b9af --- /dev/null +++ b/packages/client/src/user/index.md @@ -0,0 +1,23 @@ +--- +nav: + path: /client +group: + title: Client + path: /client +--- + +# User + +`plugin-users` 插件的前端模块 + +## CurrentUser + +当前用户 + +## SigninPage + +登录页 + +## SignupPage + +注册页 diff --git a/packages/client/src/user/index.ts b/packages/client/src/user/index.ts new file mode 100644 index 000000000..196f02b78 --- /dev/null +++ b/packages/client/src/user/index.ts @@ -0,0 +1,4 @@ +export * from './CurrentUser'; +export * from './SigninPage'; +export * from './SignupPage'; +