feat: improve document title and page title support
This commit is contained in:
parent
a1ccc82fa1
commit
d94874d345
@ -26,6 +26,7 @@
|
|||||||
"ahooks": "^3.0.5",
|
"ahooks": "^3.0.5",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"i18next": "^21.6.0",
|
"i18next": "^21.6.0",
|
||||||
|
"react-helmet": "^6.1.0",
|
||||||
"react-i18next": "^11.15.1"
|
"react-i18next": "^11.15.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
@ -18,6 +18,8 @@ import {
|
|||||||
CollectionManagerShortcut,
|
CollectionManagerShortcut,
|
||||||
ACLShortcut,
|
ACLShortcut,
|
||||||
SystemSettingsShortcut,
|
SystemSettingsShortcut,
|
||||||
|
DocumentTitleProvider,
|
||||||
|
Page,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
import { Spin } from 'antd';
|
import { Spin } from 'antd';
|
||||||
@ -33,7 +35,8 @@ const providers = [
|
|||||||
PluginManagerProvider,
|
PluginManagerProvider,
|
||||||
{ components: { ACLShortcut, DesignableSwitch, CollectionManagerShortcut, SystemSettingsShortcut } },
|
{ components: { ACLShortcut, DesignableSwitch, CollectionManagerShortcut, SystemSettingsShortcut } },
|
||||||
],
|
],
|
||||||
[SchemaComponentProvider, { components: { Menu, Action } }],
|
[SchemaComponentProvider, { components: { Page, Menu, Action } }],
|
||||||
|
[DocumentTitleProvider, { addonAfter: 'NocoBase' }],
|
||||||
[RouteSwitchProvider, { components: { AuthLayout, AdminLayout } }],
|
[RouteSwitchProvider, { components: { AuthLayout, AdminLayout } }],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -112,8 +112,7 @@ export default (apiClient: APIClient) => {
|
|||||||
type: 'void',
|
type: 'void',
|
||||||
name: name,
|
name: name,
|
||||||
'x-uid': name,
|
'x-uid': name,
|
||||||
'x-component': 'div',
|
'x-component': 'Page',
|
||||||
'x-content': name,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return [200, response];
|
return [200, response];
|
||||||
|
@ -5,4 +5,17 @@ group:
|
|||||||
path: /client
|
path: /client
|
||||||
---
|
---
|
||||||
|
|
||||||
# DocumentTitle <Badge>待定</Badge>
|
# DocumentTitle
|
||||||
|
|
||||||
|
## DocumentTitleProvider
|
||||||
|
|
||||||
|
```tsx | pure
|
||||||
|
<DocumentTitleProvider addonBefore={'!!'} addonAfter={'NocoBase'}>
|
||||||
|
</DocumentTitleProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
## useDocumentTitle
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { title, setTitle } = useDocumentTitle();
|
||||||
|
```
|
||||||
|
@ -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<DocumentTitleContextProps>({
|
||||||
|
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 (
|
||||||
|
<DocumentTitleContext.Provider
|
||||||
|
value={{
|
||||||
|
title,
|
||||||
|
setTitle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Helmet>
|
||||||
|
<title>{documentTitle}</title>
|
||||||
|
</Helmet>
|
||||||
|
{props.children}
|
||||||
|
</DocumentTitleContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDocumentTitle = () => {
|
||||||
|
return useContext(DocumentTitleContext);
|
||||||
|
};
|
||||||
|
@ -2,18 +2,20 @@ import React, { useRef, useState } from 'react';
|
|||||||
import { Button, Layout } from 'antd';
|
import { Button, Layout } from 'antd';
|
||||||
import { useRoute } from '../../hooks';
|
import { useRoute } from '../../hooks';
|
||||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
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) {
|
export function AdminLayout(props: any) {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const match = useRouteMatch<any>();
|
const match = useRouteMatch<any>();
|
||||||
|
const { setTitle } = useDocumentTitle();
|
||||||
const sideMenuRef = useRef();
|
const sideMenuRef = useRef();
|
||||||
const defaultSelectedUid = match.params.name;
|
const defaultSelectedUid = match.params.name;
|
||||||
const [schema, setSchema] = useState({});
|
const [schema, setSchema] = useState({});
|
||||||
const onSelect = ({ item }) => {
|
const onSelect = ({ item }) => {
|
||||||
const schema = item.props.schema;
|
const schema = item.props.schema;
|
||||||
setSchema(schema);
|
setSchema(schema);
|
||||||
|
setTitle(schema.title);
|
||||||
history.push(`/admin/${schema['x-uid']}`);
|
history.push(`/admin/${schema['x-uid']}`);
|
||||||
};
|
};
|
||||||
const [hidden, setHidden] = useState(false);
|
const [hidden, setHidden] = useState(false);
|
||||||
@ -38,6 +40,7 @@ export function AdminLayout(props: any) {
|
|||||||
const s = findMenuItem(data?.data);
|
const s = findMenuItem(data?.data);
|
||||||
if (s) {
|
if (s) {
|
||||||
setSchema(s);
|
setSchema(s);
|
||||||
|
setTitle(s.title);
|
||||||
history.push(`/admin/${s['x-uid']}`);
|
history.push(`/admin/${s['x-uid']}`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -11,4 +11,5 @@ export * from './input';
|
|||||||
export * from './input-number';
|
export * from './input-number';
|
||||||
export * from './password';
|
export * from './password';
|
||||||
export * from './radio';
|
export * from './radio';
|
||||||
export * from './menu';
|
export * from './menu';
|
||||||
|
export * from './page';
|
||||||
|
21
packages/client/src/schema-component/antd/page/Page.tsx
Normal file
21
packages/client/src/schema-component/antd/page/Page.tsx
Normal file
@ -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 (
|
||||||
|
<>
|
||||||
|
<AntdPageHeader ghost={false} title={title} {...others} />
|
||||||
|
<div style={{ margin: 24 }}>{children}</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -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 (
|
||||||
|
<SchemaComponentProvider components={{ Page }}>
|
||||||
|
<DocumentTitleProvider addonAfter={'NocoBase'}>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</DocumentTitleProvider>
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
);
|
||||||
|
};
|
12
packages/client/src/schema-component/antd/page/index.md
Normal file
12
packages/client/src/schema-component/antd/page/index.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
nav:
|
||||||
|
path: /client
|
||||||
|
group:
|
||||||
|
path: /schema-components
|
||||||
|
---
|
||||||
|
|
||||||
|
# Page
|
||||||
|
|
||||||
|
可以与 DocumentTitleProvider 搭配使用,将 page title 显示在 document.title 上。
|
||||||
|
|
||||||
|
<code src="./demos/demo1.tsx" />
|
1
packages/client/src/schema-component/antd/page/index.ts
Normal file
1
packages/client/src/schema-component/antd/page/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Page';
|
20
yarn.lock
20
yarn.lock
@ -12282,6 +12282,21 @@ react-dom@^17.0.1:
|
|||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
scheduler "^0.20.2"
|
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:
|
react-i18next@^11.15.1:
|
||||||
version "11.15.1"
|
version "11.15.1"
|
||||||
resolved "https://registry.npmjs.org/react-i18next/-/react-i18next-11.15.1.tgz#1dec5f2bf2cf7bc5043e9fcb391c9d6e24bb9bfe"
|
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-invariant "^1.0.2"
|
||||||
tiny-warning "^1.0.0"
|
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:
|
react-sortable-hoc@^1.11.0:
|
||||||
version "1.11.0"
|
version "1.11.0"
|
||||||
resolved "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-1.11.0.tgz#fe4022362bbafc4b836f5104b9676608a40a278f"
|
resolved "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-1.11.0.tgz#fe4022362bbafc4b836f5104b9676608a40a278f"
|
||||||
|
Loading…
Reference in New Issue
Block a user