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"