diff --git a/packages/client/package.json b/packages/client/package.json index 237b066451..7e78db20b8 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -26,6 +26,7 @@ "ahooks": "^3.0.5", "axios": "^0.24.0", "i18next": "^21.6.0", + "react-helmet": "^6.1.0", "react-i18next": "^11.15.1" }, "peerDependencies": { diff --git a/packages/client/src/application/demos/demo2/index.tsx b/packages/client/src/application/demos/demo2/index.tsx index 137eef4e71..5bfe6ac67c 100644 --- a/packages/client/src/application/demos/demo2/index.tsx +++ b/packages/client/src/application/demos/demo2/index.tsx @@ -18,6 +18,8 @@ import { CollectionManagerShortcut, ACLShortcut, SystemSettingsShortcut, + DocumentTitleProvider, + Page, } from '@nocobase/client'; import { I18nextProvider } from 'react-i18next'; import { Spin } from 'antd'; @@ -33,7 +35,8 @@ const providers = [ PluginManagerProvider, { components: { ACLShortcut, DesignableSwitch, CollectionManagerShortcut, SystemSettingsShortcut } }, ], - [SchemaComponentProvider, { components: { Menu, Action } }], + [SchemaComponentProvider, { components: { Page, Menu, Action } }], + [DocumentTitleProvider, { addonAfter: 'NocoBase' }], [RouteSwitchProvider, { components: { AuthLayout, AdminLayout } }], ]; diff --git a/packages/client/src/application/demos/demo2/mock.ts b/packages/client/src/application/demos/demo2/mock.ts index 3572d9c8ae..c5b6b7d63c 100644 --- a/packages/client/src/application/demos/demo2/mock.ts +++ b/packages/client/src/application/demos/demo2/mock.ts @@ -112,8 +112,7 @@ export default (apiClient: APIClient) => { type: 'void', name: name, 'x-uid': name, - 'x-component': 'div', - 'x-content': name, + 'x-component': 'Page', }, }; return [200, response]; diff --git a/packages/client/src/document-title/index.md b/packages/client/src/document-title/index.md index f580fa0790..d515153585 100644 --- a/packages/client/src/document-title/index.md +++ b/packages/client/src/document-title/index.md @@ -5,4 +5,17 @@ group: path: /client --- -# DocumentTitle 待定 +# DocumentTitle + +## DocumentTitleProvider + +```tsx | pure + + +``` + +## useDocumentTitle + +```ts +const { title, setTitle } = useDocumentTitle(); +``` diff --git a/packages/client/src/document-title/index.tsx b/packages/client/src/document-title/index.tsx index dd66875782..bf68d5fb4c 100644 --- a/packages/client/src/document-title/index.tsx +++ b/packages/client/src/document-title/index.tsx @@ -1,3 +1,37 @@ -export function DocumentTitleProvider() { +import React, { createContext, useContext, useState } from 'react'; +import { Helmet } from 'react-helmet'; +interface DocumentTitleContextProps { + title?: any; + setTitle?: (title?: any) => void; } + +export const DocumentTitleContext = createContext({ + title: null, + setTitle() {}, +}); + +export const DocumentTitleProvider: React.FC<{ addonBefore?: string; addonAfter?: string }> = (props) => { + const { addonBefore, addonAfter } = props; + const [title, setTitle] = useState(''); + const documentTitle = `${addonBefore ? ` - ${addonBefore}` : ''}${title || ''}${ + addonAfter ? ` - ${addonAfter}` : '' + }`; + return ( + + + {documentTitle} + + {props.children} + + ); +}; + +export const useDocumentTitle = () => { + return useContext(DocumentTitleContext); +}; 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 0ad940f765..2908829baa 100644 --- a/packages/client/src/route-switch/antd/admin-layout/index.tsx +++ b/packages/client/src/route-switch/antd/admin-layout/index.tsx @@ -2,18 +2,20 @@ import React, { useRef, useState } from 'react'; import { Button, Layout } from 'antd'; import { useRoute } from '../../hooks'; import { useHistory, useRouteMatch } from 'react-router-dom'; -import { findMenuItem, RemoteSchemaComponent, PluginManager, CurrentUser } from '../../../'; +import { findMenuItem, RemoteSchemaComponent, PluginManager, CurrentUser, useDocumentTitle } from '../../../'; export function AdminLayout(props: any) { const route = useRoute(); const history = useHistory(); const match = useRouteMatch(); + const { setTitle } = useDocumentTitle(); const sideMenuRef = useRef(); const defaultSelectedUid = match.params.name; const [schema, setSchema] = useState({}); const onSelect = ({ item }) => { const schema = item.props.schema; setSchema(schema); + setTitle(schema.title); history.push(`/admin/${schema['x-uid']}`); }; const [hidden, setHidden] = useState(false); @@ -38,6 +40,7 @@ export function AdminLayout(props: any) { const s = findMenuItem(data?.data); if (s) { setSchema(s); + setTitle(s.title); history.push(`/admin/${s['x-uid']}`); } }} diff --git a/packages/client/src/schema-component/antd/index.ts b/packages/client/src/schema-component/antd/index.ts index 556555fd62..e2e05de797 100644 --- a/packages/client/src/schema-component/antd/index.ts +++ b/packages/client/src/schema-component/antd/index.ts @@ -11,4 +11,5 @@ export * from './input'; export * from './input-number'; export * from './password'; export * from './radio'; -export * from './menu'; \ No newline at end of file +export * from './menu'; +export * from './page'; diff --git a/packages/client/src/schema-component/antd/page/Page.tsx b/packages/client/src/schema-component/antd/page/Page.tsx new file mode 100644 index 0000000000..f43ac76a97 --- /dev/null +++ b/packages/client/src/schema-component/antd/page/Page.tsx @@ -0,0 +1,21 @@ +import React, { useEffect } from 'react'; +import { PageHeader as AntdPageHeader } from 'antd'; +import { observer, useField } from '@formily/react'; +import { useDocumentTitle } from '@nocobase/client'; + +export const Page = (props) => { + const { children, ...others } = props; + const field = useField(); + const { title, setTitle } = useDocumentTitle(); + useEffect(() => { + if (!title) { + setTitle(field.title); + } + }, [field.title, title]); + return ( + <> + +
{children}
+ + ); +}; diff --git a/packages/client/src/schema-component/antd/page/demos/demo1.tsx b/packages/client/src/schema-component/antd/page/demos/demo1.tsx new file mode 100644 index 0000000000..a0b3a560ed --- /dev/null +++ b/packages/client/src/schema-component/antd/page/demos/demo1.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { ISchema } from '@formily/react'; +import { Page, DocumentTitleProvider, SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const schema: ISchema = { + type: 'object', + properties: { + page1: { + type: 'void', + 'x-component': 'Page', + title: 'Page Title', + properties: { + content: { + type: 'void', + 'x-component': 'div', + 'x-content': 'Page Content', + }, + }, + }, + }, +}; + +export default () => { + return ( + + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/page/index.md b/packages/client/src/schema-component/antd/page/index.md new file mode 100644 index 0000000000..45f117b937 --- /dev/null +++ b/packages/client/src/schema-component/antd/page/index.md @@ -0,0 +1,12 @@ +--- +nav: + path: /client +group: + path: /schema-components +--- + +# Page + +可以与 DocumentTitleProvider 搭配使用,将 page title 显示在 document.title 上。 + + diff --git a/packages/client/src/schema-component/antd/page/index.ts b/packages/client/src/schema-component/antd/page/index.ts new file mode 100644 index 0000000000..d9925d7520 --- /dev/null +++ b/packages/client/src/schema-component/antd/page/index.ts @@ -0,0 +1 @@ +export * from './Page'; diff --git a/yarn.lock b/yarn.lock index 54a6fa9535..cddeff563d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12282,6 +12282,21 @@ react-dom@^17.0.1: object-assign "^4.1.1" scheduler "^0.20.2" +react-fast-compare@^3.1.1: + version "3.2.0" + resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" + integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== + +react-helmet@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" + integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== + dependencies: + object-assign "^4.1.1" + prop-types "^15.7.2" + react-fast-compare "^3.1.1" + react-side-effect "^2.1.0" + react-i18next@^11.15.1: version "11.15.1" resolved "https://registry.npmjs.org/react-i18next/-/react-i18next-11.15.1.tgz#1dec5f2bf2cf7bc5043e9fcb391c9d6e24bb9bfe" @@ -12342,6 +12357,11 @@ react-router@5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-side-effect@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" + integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== + react-sortable-hoc@^1.11.0: version "1.11.0" resolved "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-1.11.0.tgz#fe4022362bbafc4b836f5104b9676608a40a278f"