mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 05:29:26 +08:00
chore: user center extension (#6213)
* chore(versions): 😊 publish v1.6.0-alpha.24 * chore(versions): 😊 publish v1.6.0-alpha.25 * chore: user center extension * refactor: change password * chore: theme * chore: language * chore: code improve * feat: support extending frontend filter operators (#6085) * feat: operator extension * fix: bug * refactor: code improve * fix: jsonLogic --------- Co-authored-by: chenos <chenlinxh@gmail.com> * refactor: remove registerOperators (#6224) * refactor(plugin-workflow): trigger workflow action settings (#6143) * refactor(plugin-workflow): move bind workflow settings to plugin * refactor(plugin-block-workbench): move component to core * refactor(plugin-block-workbench): adjust component api * fix(plugin-workflow-action-trigger): fix test cases * fix(plugin-workflow): fix component scope * fix(plugin-workflow-action-trigger): fix test cases * chore(versions): 😊 publish v1.6.0-alpha.26 * feat: support the extension of preset fields in collections (#6183) * feat: support the extension of preset fields in collections * fix: bug * fix: bug * fix: bug * refactor: create collection * fix: config * fix: test case * refactor: code improve * refactor: code improve * fix: bug * fix: bug --------- Co-authored-by: chenos <chenlinxh@gmail.com> * feat: support for the extension of optional fields for Kanban, Calendar, and Formula Field plugins (#6076) * feat: kanban field extention * fix: bug * fix: bug * fix: bug * fix: bug * feat: calender title fields * feat: background color fields * fix: bug * fix: bug * feat: formula field expression support field * feat: preset fields * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * refactor: code improve * fix: bug * fix: bug * fix: bug * fix: bug * refactor: code improve * revert: preset fields * refactor: code improve * refactor: code improve * fix: bug * fix: bug * fix: bug * refactor: code improve * fix: bug * refactor: code improve * refactor: code improve * fix: bug * fix: locale * refactor: code improve * fix: bug * refactor: code improve * refactor: code improve * refactor: code improve * refactor: locale * fix: test * fix: bug * fix: test * fix: test --------- Co-authored-by: chenos <chenlinxh@gmail.com> * feat: inline mode * chore(versions): 😊 publish v1.6.0-alpha.27 * fix(data-source-main): update order * fix: bug * fix: bug * refactor: code improve * fix: bug * fix: code improve * fix: bug * fix: improve code * fix: getFontColor (#6241) * chore(versions): 😊 publish v1.6.0-alpha.28 * refactor: code improve * fix: bug * refactor: code improve * fix: bug * fix: print action e2e test (#6256) * fix: print action e2e test * fix: test * fix: merge bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * refactor: update package.json * fix: bug * feat: code improve --------- Co-authored-by: nocobase[bot] <179432756+nocobase[bot]@users.noreply.github.com> Co-authored-by: chenos <chenlinxh@gmail.com> Co-authored-by: Junyi <mytharcher@users.noreply.github.com>
This commit is contained in:
parent
c9c7531946
commit
9eb9d4e000
@ -2,9 +2,7 @@
|
|||||||
"version": "1.6.0-beta.9",
|
"version": "1.6.0-beta.9",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"npmClientArgs": [
|
"npmClientArgs": ["--ignore-engines"],
|
||||||
"--ignore-engines"
|
|
||||||
],
|
|
||||||
"command": {
|
"command": {
|
||||||
"version": {
|
"version": {
|
||||||
"forcePublish": true,
|
"forcePublish": true,
|
||||||
|
@ -101,7 +101,7 @@ const Demo = () => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>{render()}</div>
|
<div>{render()}</div>
|
||||||
<div>可以进行参数的二次覆盖:{render({ style: { color: 'red' } })}</div>
|
<div>可以进行参数的二次覆盖:{render({ mode: 'inline', style: { color: 'red' } })}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -65,7 +65,8 @@
|
|||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.11.2",
|
||||||
"react-to-print": "^2.14.7",
|
"react-to-print": "^2.14.7",
|
||||||
"sanitize-html": "2.13.0",
|
"sanitize-html": "2.13.0",
|
||||||
"use-deep-compare-effect": "^1.8.1"
|
"use-deep-compare-effect": "^1.8.1",
|
||||||
|
"ignore": "^5.2.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=18.0.0",
|
"react": ">=18.0.0",
|
||||||
|
@ -17,7 +17,6 @@ import React, { ComponentType, FC, ReactElement, ReactNode } from 'react';
|
|||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
import { Link, NavLink, Navigate } from 'react-router-dom';
|
import { Link, NavLink, Navigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { APIClient, APIClientProvider } from '../api-client';
|
import { APIClient, APIClientProvider } from '../api-client';
|
||||||
import { CSSVariableProvider } from '../css-variable';
|
import { CSSVariableProvider } from '../css-variable';
|
||||||
import { AntdAppProvider, GlobalThemeProvider } from '../global-theme';
|
import { AntdAppProvider, GlobalThemeProvider } from '../global-theme';
|
||||||
@ -29,7 +28,8 @@ import { WebSocketClient, WebSocketClientOptions } from './WebSocketClient';
|
|||||||
import { AppComponent, BlankComponent, defaultAppComponents } from './components';
|
import { AppComponent, BlankComponent, defaultAppComponents } from './components';
|
||||||
import { SchemaInitializer, SchemaInitializerManager } from './schema-initializer';
|
import { SchemaInitializer, SchemaInitializerManager } from './schema-initializer';
|
||||||
import * as schemaInitializerComponents from './schema-initializer/components';
|
import * as schemaInitializerComponents from './schema-initializer/components';
|
||||||
import { SchemaSettings, SchemaSettingsManager } from './schema-settings';
|
import { SchemaSettings, SchemaSettingsManager, SchemaSettingsItemType } from './schema-settings';
|
||||||
|
|
||||||
import { compose, normalizeContainer } from './utils';
|
import { compose, normalizeContainer } from './utils';
|
||||||
import { defineGlobalDeps } from './utils/globalDeps';
|
import { defineGlobalDeps } from './utils/globalDeps';
|
||||||
import { getRequireJs } from './utils/requirejs';
|
import { getRequireJs } from './utils/requirejs';
|
||||||
@ -46,6 +46,7 @@ import { AppSchemaComponentProvider } from './AppSchemaComponentProvider';
|
|||||||
import type { Plugin } from './Plugin';
|
import type { Plugin } from './Plugin';
|
||||||
import { getOperators } from './globalOperators';
|
import { getOperators } from './globalOperators';
|
||||||
import type { RequireJS } from './utils/requirejs';
|
import type { RequireJS } from './utils/requirejs';
|
||||||
|
import { useAclSnippets } from './hooks/useAclSnippets';
|
||||||
|
|
||||||
type JsonLogic = {
|
type JsonLogic = {
|
||||||
addOperation: (name: string, fn?: any) => void;
|
addOperation: (name: string, fn?: any) => void;
|
||||||
@ -495,4 +496,20 @@ export class Application {
|
|||||||
getGlobalVar(key) {
|
getGlobalVar(key) {
|
||||||
return get(this.globalVars, key);
|
return get(this.globalVars, key);
|
||||||
}
|
}
|
||||||
|
addUserCenterSettingsItem(item: SchemaSettingsItemType & { aclSnippet?: string }) {
|
||||||
|
const useVisibleProp = item.useVisible || (() => true);
|
||||||
|
const useVisible = () => {
|
||||||
|
const { allow } = useAclSnippets();
|
||||||
|
const visible = useVisibleProp();
|
||||||
|
if (!visible) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return item.aclSnippet ? allow(item.aclSnippet) : true;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.schemaSettingsManager.addItem('userCenterSettings', item.name, {
|
||||||
|
...item,
|
||||||
|
useVisible: useVisible,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
25
packages/core/client/src/application/hooks/useAclSnippets.ts
Normal file
25
packages/core/client/src/application/hooks/useAclSnippets.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useACLRoleContext } from '../../acl/ACLProvider';
|
||||||
|
import ignore from 'ignore';
|
||||||
|
|
||||||
|
export const useAclSnippets = () => {
|
||||||
|
const { allowAll, snippets } = useACLRoleContext();
|
||||||
|
return {
|
||||||
|
allow: (aclSnippet) => {
|
||||||
|
if (aclSnippet) {
|
||||||
|
const ig = ignore().add(snippets);
|
||||||
|
const appAllowed = allowAll || ig.ignores(aclSnippet);
|
||||||
|
return appAllowed;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -7,16 +7,15 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { useApp } from '../../hooks';
|
|
||||||
import { SchemaSettingOptions } from '../types';
|
|
||||||
import React from 'react';
|
|
||||||
import { SchemaSettingsWrapper } from '../components';
|
|
||||||
import { SchemaSettingsProps } from '../../../schema-settings';
|
|
||||||
import { Schema } from '@formily/json-schema';
|
|
||||||
import { GeneralField } from '@formily/core';
|
import { GeneralField } from '@formily/core';
|
||||||
|
import { Schema } from '@formily/json-schema';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
import { Designable } from '../../../schema-component';
|
import { Designable } from '../../../schema-component';
|
||||||
|
import { SchemaSettingsProps } from '../../../schema-settings';
|
||||||
|
import { useApp } from '../../hooks';
|
||||||
|
import { SchemaSettingsWrapper } from '../components';
|
||||||
import { SchemaSettings } from '../SchemaSettings';
|
import { SchemaSettings } from '../SchemaSettings';
|
||||||
|
import { SchemaSettingOptions } from '../types';
|
||||||
|
|
||||||
type UseSchemaSettingsRenderOptions<T = {}> = Omit<SchemaSettingOptions<T>, 'name' | 'items'> &
|
type UseSchemaSettingsRenderOptions<T = {}> = Omit<SchemaSettingOptions<T>, 'name' | 'items'> &
|
||||||
Omit<SchemaSettingsProps, 'title' | 'children'> & {
|
Omit<SchemaSettingsProps, 'title' | 'children'> & {
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
|
|
||||||
export interface SchemaSettingOptions<T = {}> {
|
export interface SchemaSettingOptions<T = {}> {
|
||||||
name: string;
|
name: string;
|
||||||
|
mode?: 'inline' | 'dropdown';
|
||||||
Component?: ComponentType<T>;
|
Component?: ComponentType<T>;
|
||||||
componentProps?: T;
|
componentProps?: T;
|
||||||
items: SchemaSettingsItemType[];
|
items: SchemaSettingsItemType[];
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useAPIClient, useSystemSettings, SchemaSettingsSelectItem } from '../../..';
|
||||||
|
import locale from '../../../locale';
|
||||||
|
|
||||||
|
export const LanguageSettings = () => {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { data } = useSystemSettings() || {};
|
||||||
|
const enabledLanguages: string[] = useMemo(() => data?.data?.enabledLanguages || [], [data?.data?.enabledLanguages]);
|
||||||
|
if (enabledLanguages.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SchemaSettingsSelectItem
|
||||||
|
title={t('Language')}
|
||||||
|
options={Object.keys(locale)
|
||||||
|
.filter((lang) => enabledLanguages.includes(lang))
|
||||||
|
.map((lang) => {
|
||||||
|
return {
|
||||||
|
label: locale[lang].label,
|
||||||
|
value: lang,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
value={i18n.language}
|
||||||
|
onChange={async (lang) => {
|
||||||
|
await api.resource('users').updateLang({
|
||||||
|
values: {
|
||||||
|
appLang: lang,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
api.auth.setLocale(lang);
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { UserOutlined } from '@ant-design/icons';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { useToken, useSchemaSettingsRender } from '../../../';
|
||||||
|
|
||||||
|
export const UserCenterButton = () => {
|
||||||
|
const { token } = useToken();
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="nb-user-center"
|
||||||
|
style={{ display: 'inline-block', verticalAlign: 'top', width: '46px', height: '46px' }}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
data-testid="user-center-button"
|
||||||
|
className={css`
|
||||||
|
max-width: 160px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`}
|
||||||
|
style={{ cursor: 'pointer', padding: '16px', color: token.colorTextHeaderMenu }}
|
||||||
|
>
|
||||||
|
<UserOutlined />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function UserCenter() {
|
||||||
|
const { render } = useSchemaSettingsRender('userCenterSettings');
|
||||||
|
return <div style={{ display: 'inline-block' }}>{render()}</div>;
|
||||||
|
}
|
@ -27,7 +27,6 @@ import { Outlet } from 'react-router-dom';
|
|||||||
import {
|
import {
|
||||||
ACLRolesCheckProvider,
|
ACLRolesCheckProvider,
|
||||||
CurrentAppInfoProvider,
|
CurrentAppInfoProvider,
|
||||||
CurrentUser,
|
|
||||||
findByUid,
|
findByUid,
|
||||||
findMenuItem,
|
findMenuItem,
|
||||||
NavigateIfNotSignIn,
|
NavigateIfNotSignIn,
|
||||||
@ -58,7 +57,8 @@ import { useMenuTranslation } from '../../../schema-component/antd/menu/locale';
|
|||||||
import { Help } from '../../../user/Help';
|
import { Help } from '../../../user/Help';
|
||||||
import { KeepAlive } from './KeepAlive';
|
import { KeepAlive } from './KeepAlive';
|
||||||
import { convertRoutesToSchema, NocoBaseDesktopRoute, NocoBaseDesktopRouteType } from './convertRoutesToSchema';
|
import { convertRoutesToSchema, NocoBaseDesktopRoute, NocoBaseDesktopRouteType } from './convertRoutesToSchema';
|
||||||
|
import { userCenterSettings } from './userCenterSettings';
|
||||||
|
import { UserCenter } from './UserCenterButton';
|
||||||
export { KeepAlive, NocoBaseDesktopRouteType };
|
export { KeepAlive, NocoBaseDesktopRouteType };
|
||||||
|
|
||||||
const RouteContext = createContext<NocoBaseDesktopRoute | null>(null);
|
const RouteContext = createContext<NocoBaseDesktopRoute | null>(null);
|
||||||
@ -529,7 +529,7 @@ export const InternalAdminLayout = () => {
|
|||||||
<Divider type="vertical" />
|
<Divider type="vertical" />
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
<Help />
|
<Help />
|
||||||
<CurrentUser />
|
<UserCenter />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout.Header>
|
</Layout.Header>
|
||||||
@ -574,6 +574,7 @@ export class AdminLayoutPlugin extends Plugin {
|
|||||||
await this.app.pm.add(RemoteSchemaTemplateManagerPlugin);
|
await this.app.pm.add(RemoteSchemaTemplateManagerPlugin);
|
||||||
}
|
}
|
||||||
async load() {
|
async load() {
|
||||||
|
this.app.schemaSettingsManager.add(userCenterSettings);
|
||||||
this.app.addComponents({ AdminLayout, AdminDynamicPage });
|
this.app.addComponents({ AdminLayout, AdminDynamicPage });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { UserCenterButton } from './UserCenterButton';
|
||||||
|
import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings';
|
||||||
|
import { LanguageSettings } from './LanguageSettings';
|
||||||
|
|
||||||
|
const userCenterSettings = new SchemaSettings({
|
||||||
|
name: 'userCenterSettings',
|
||||||
|
Component: UserCenterButton,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'langue',
|
||||||
|
Component: LanguageSettings,
|
||||||
|
sort: 350,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export { userCenterSettings };
|
@ -22,6 +22,7 @@ import {
|
|||||||
CascaderProps,
|
CascaderProps,
|
||||||
ConfigProvider,
|
ConfigProvider,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
|
Menu,
|
||||||
MenuItemProps,
|
MenuItemProps,
|
||||||
MenuProps,
|
MenuProps,
|
||||||
Modal,
|
Modal,
|
||||||
@ -119,6 +120,7 @@ export interface SchemaSettingsProps {
|
|||||||
field?: GeneralField;
|
field?: GeneralField;
|
||||||
fieldSchema?: Schema;
|
fieldSchema?: Schema;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
mode?: 'inline' | 'dropdown';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SchemaSettingsContextProps<T = any> {
|
interface SchemaSettingsContextProps<T = any> {
|
||||||
@ -167,7 +169,7 @@ export const SchemaSettingsProvider: React.FC<SchemaSettingsProviderProps> = (pr
|
|||||||
return <SchemaSettingsContext.Provider value={value}>{children}</SchemaSettingsContext.Provider>;
|
return <SchemaSettingsContext.Provider value={value}>{children}</SchemaSettingsContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SchemaSettingsDropdown: React.FC<SchemaSettingsProps> = React.memo((props) => {
|
const InternalSchemaSettingsDropdown: React.FC<SchemaSettingsProps> = React.memo((props) => {
|
||||||
const { title, dn, ...others } = props;
|
const { title, dn, ...others } = props;
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const { Component, getMenuItems } = useMenuItem();
|
const { Component, getMenuItems } = useMenuItem();
|
||||||
@ -232,6 +234,25 @@ export const SchemaSettingsDropdown: React.FC<SchemaSettingsProps> = React.memo(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const InternalSchemaSettingsMenu: React.FC<SchemaSettingsProps> = React.memo((props) => {
|
||||||
|
const { title, dn, ...others } = props;
|
||||||
|
const [visible, setVisible] = useState(true);
|
||||||
|
const { Component, getMenuItems } = useMenuItem();
|
||||||
|
const items = getMenuItems(() => props.children);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaSettingsProvider visible={visible} setVisible={setVisible} dn={dn} {...others}>
|
||||||
|
<Component />
|
||||||
|
<Menu items={items} />
|
||||||
|
</SchemaSettingsProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SchemaSettingsDropdown: React.FC<SchemaSettingsProps> = React.memo((props) => {
|
||||||
|
const { mode } = props;
|
||||||
|
return mode === 'inline' ? <InternalSchemaSettingsMenu {...props} /> : <InternalSchemaSettingsDropdown {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
SchemaSettingsDropdown.displayName = 'SchemaSettingsDropdown';
|
SchemaSettingsDropdown.displayName = 'SchemaSettingsDropdown';
|
||||||
|
|
||||||
const findGridSchema = (fieldSchema) => {
|
const findGridSchema = (fieldSchema) => {
|
||||||
|
@ -7,219 +7,12 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { UserOutlined } from '@ant-design/icons';
|
import { createContext } from 'react';
|
||||||
import { css } from '@emotion/css';
|
import { SelectWithTitle } from '../common';
|
||||||
import { error } from '@nocobase/utils/client';
|
|
||||||
import { App, Dropdown, Menu, MenuProps } from 'antd';
|
|
||||||
import React, { createContext, useCallback, useMemo as useEffect, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useACLRoleContext, useAPIClient, useCurrentUserContext, useToken } from '..';
|
|
||||||
import { useNavigateNoUpdate } from '../application/CustomRouterContextProvider';
|
|
||||||
import { useChangePassword } from './ChangePassword';
|
|
||||||
import { useCurrentUserSettingsMenu } from './CurrentUserSettingsMenuProvider';
|
|
||||||
import { useEditProfile } from './EditProfile';
|
|
||||||
import { useLanguageSettings } from './LanguageSettings';
|
|
||||||
import { useSwitchRole } from './SwitchRole';
|
|
||||||
|
|
||||||
const useNickname = () => {
|
export const SettingsMenuProvider = (props) => {
|
||||||
const { data } = useCurrentUserContext();
|
return SelectWithTitle;
|
||||||
const { token } = useToken();
|
|
||||||
|
|
||||||
return useEffect(() => {
|
|
||||||
return {
|
|
||||||
key: 'nickname',
|
|
||||||
disabled: true,
|
|
||||||
label: (
|
|
||||||
<span aria-disabled="false" style={{ cursor: 'text', color: token.colorTextDescription }}>
|
|
||||||
{data?.data?.nickname || data?.data?.username || data?.data?.email}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}, [data?.data?.email, data?.data?.nickname, data?.data?.username, data?.data.version, token.colorTextDescription]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @note If you want to change here, Note the Setting block on the mobile side
|
|
||||||
*/
|
|
||||||
export const SettingsMenu: React.FC<{
|
|
||||||
redirectUrl?: string;
|
|
||||||
}> = (props) => {
|
|
||||||
const { addMenuItem, getMenuItems } = useCurrentUserSettingsMenu();
|
|
||||||
const { redirectUrl = '' } = props;
|
|
||||||
const { allowAll, snippets } = useACLRoleContext();
|
|
||||||
const appAllowed = allowAll || snippets?.includes('app');
|
|
||||||
const navigate = useNavigateNoUpdate();
|
|
||||||
const api = useAPIClient();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const silenceApi = useAPIClient();
|
|
||||||
const check = useCallback(async () => {
|
|
||||||
return await new Promise((resolve) => {
|
|
||||||
const heartbeat = setInterval(() => {
|
|
||||||
silenceApi
|
|
||||||
.silent()
|
|
||||||
.resource('app')
|
|
||||||
.getInfo()
|
|
||||||
.then((res) => {
|
|
||||||
if (res?.status === 200) {
|
|
||||||
resolve('ok');
|
|
||||||
clearInterval(heartbeat);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
error(err);
|
|
||||||
});
|
|
||||||
}, 3000);
|
|
||||||
});
|
|
||||||
}, [silenceApi]);
|
|
||||||
const nickname = useNickname();
|
|
||||||
const editProfile = useEditProfile();
|
|
||||||
const changePassword = useChangePassword();
|
|
||||||
const switchRole = useSwitchRole();
|
|
||||||
const languageSettings = useLanguageSettings();
|
|
||||||
const { modal } = App.useApp();
|
|
||||||
const controlApp = useEffect<MenuProps['items']>(() => {
|
|
||||||
if (!appAllowed) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: 'cache',
|
|
||||||
label: t('Clear cache'),
|
|
||||||
onClick: async () => {
|
|
||||||
await api.resource('app').clearCache();
|
|
||||||
window.location.reload();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'reboot',
|
|
||||||
label: t('Restart application'),
|
|
||||||
onClick: async () => {
|
|
||||||
modal.confirm({
|
|
||||||
title: t('Restart application'),
|
|
||||||
// content: t('The will interrupt service, it may take a few seconds to restart. Are you sure to continue?'),
|
|
||||||
okText: t('Restart'),
|
|
||||||
okButtonProps: {
|
|
||||||
danger: true,
|
|
||||||
},
|
|
||||||
onOk: async () => {
|
|
||||||
await api.resource('app').restart();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'divider_4',
|
|
||||||
type: 'divider',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [api, appAllowed, check, modal, t]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const items = [
|
|
||||||
nickname,
|
|
||||||
{
|
|
||||||
key: 'divider_1',
|
|
||||||
type: 'divider',
|
|
||||||
},
|
|
||||||
editProfile,
|
|
||||||
changePassword,
|
|
||||||
editProfile ||
|
|
||||||
(changePassword && {
|
|
||||||
key: 'divider_2',
|
|
||||||
type: 'divider',
|
|
||||||
}),
|
|
||||||
switchRole,
|
|
||||||
{
|
|
||||||
key: 'divider_3',
|
|
||||||
type: 'divider',
|
|
||||||
},
|
|
||||||
...controlApp,
|
|
||||||
{
|
|
||||||
key: 'signout',
|
|
||||||
label: t('Sign out'),
|
|
||||||
onClick: async () => {
|
|
||||||
const { data } = await api.auth.signOut();
|
|
||||||
if (data?.data?.redirect) {
|
|
||||||
window.location.href = data.data.redirect;
|
|
||||||
} else {
|
|
||||||
navigate(`/signin?redirect=${encodeURIComponent(redirectUrl)}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
items.forEach((item) => {
|
|
||||||
if (item) {
|
|
||||||
addMenuItem(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (languageSettings) {
|
|
||||||
addMenuItem(languageSettings, { before: 'divider_3' });
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
addMenuItem,
|
|
||||||
api.auth,
|
|
||||||
editProfile,
|
|
||||||
changePassword,
|
|
||||||
controlApp,
|
|
||||||
languageSettings,
|
|
||||||
navigate,
|
|
||||||
redirectUrl,
|
|
||||||
switchRole,
|
|
||||||
t,
|
|
||||||
nickname,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return <Menu items={getMenuItems()} />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DropdownVisibleContext = createContext(null);
|
export const DropdownVisibleContext = createContext(null);
|
||||||
DropdownVisibleContext.displayName = 'DropdownVisibleContext';
|
DropdownVisibleContext.displayName = 'DropdownVisibleContext';
|
||||||
|
|
||||||
export const CurrentUser = () => {
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const { token } = useToken();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={css`
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
width: 46px;
|
|
||||||
height: 46px;
|
|
||||||
&:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.1) !important;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<DropdownVisibleContext.Provider value={{ visible, setVisible }}>
|
|
||||||
<Dropdown
|
|
||||||
open={visible}
|
|
||||||
onOpenChange={(visible) => {
|
|
||||||
setVisible(visible);
|
|
||||||
}}
|
|
||||||
dropdownRender={() => {
|
|
||||||
return <SettingsMenu />;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-testid="user-center-button"
|
|
||||||
className={css`
|
|
||||||
max-width: 160px;
|
|
||||||
overflow: hidden;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
`}
|
|
||||||
style={{ cursor: 'pointer', padding: '16px', color: token.colorTextHeaderMenu }}
|
|
||||||
>
|
|
||||||
<UserOutlined />
|
|
||||||
</span>
|
|
||||||
</Dropdown>
|
|
||||||
</DropdownVisibleContext.Provider>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is part of the NocoBase (R) project.
|
|
||||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
||||||
* Authors: NocoBase Team.
|
|
||||||
*
|
|
||||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { MenuProps } from 'antd';
|
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { SelectWithTitle, useAPIClient, useSystemSettings } from '..';
|
|
||||||
import locale from '../locale';
|
|
||||||
|
|
||||||
export const useLanguageSettings = () => {
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
const api = useAPIClient();
|
|
||||||
const { data } = useSystemSettings() || {};
|
|
||||||
const enabledLanguages: string[] = useMemo(() => data?.data?.enabledLanguages || [], [data?.data?.enabledLanguages]);
|
|
||||||
const result = useMemo<MenuProps['items'][0]>(() => {
|
|
||||||
return {
|
|
||||||
key: 'language',
|
|
||||||
eventKey: 'LanguageSettings',
|
|
||||||
label: (
|
|
||||||
<SelectWithTitle
|
|
||||||
title={t('Language')}
|
|
||||||
options={Object.keys(locale)
|
|
||||||
.filter((lang) => enabledLanguages.includes(lang))
|
|
||||||
.map((lang) => {
|
|
||||||
return {
|
|
||||||
label: locale[lang].label,
|
|
||||||
value: lang,
|
|
||||||
};
|
|
||||||
})}
|
|
||||||
defaultValue={i18n.language}
|
|
||||||
onChange={async (lang) => {
|
|
||||||
await api.resource('users').updateLang({
|
|
||||||
values: {
|
|
||||||
appLang: lang,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
api.auth.setLocale(lang);
|
|
||||||
window.location.reload();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}, [api, enabledLanguages, i18n, t]);
|
|
||||||
|
|
||||||
if (enabledLanguages.length < 2) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
@ -1,50 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is part of the NocoBase (R) project.
|
|
||||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
||||||
* Authors: NocoBase Team.
|
|
||||||
*
|
|
||||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { MenuProps } from 'antd';
|
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useAPIClient } from '../api-client';
|
|
||||||
import { SelectWithTitle } from '../common';
|
|
||||||
import { useCurrentRoles } from './CurrentUserProvider';
|
|
||||||
|
|
||||||
export const useSwitchRole = () => {
|
|
||||||
const api = useAPIClient();
|
|
||||||
const roles = useCurrentRoles();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const result = useMemo<MenuProps['items'][0]>(() => {
|
|
||||||
return {
|
|
||||||
key: 'role',
|
|
||||||
eventKey: 'SwitchRole',
|
|
||||||
label: (
|
|
||||||
<SelectWithTitle
|
|
||||||
title={t('Switch role')}
|
|
||||||
fieldNames={{
|
|
||||||
label: 'title',
|
|
||||||
value: 'name',
|
|
||||||
}}
|
|
||||||
options={roles}
|
|
||||||
defaultValue={api.auth.role}
|
|
||||||
onChange={async (roleName) => {
|
|
||||||
api.auth.setRole(roleName);
|
|
||||||
await api.resource('users').setDefaultRole({ values: { roleName } });
|
|
||||||
location.reload();
|
|
||||||
window.location.reload();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}, [api, roles, t]);
|
|
||||||
|
|
||||||
if (roles.length <= 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
@ -1,58 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is part of the NocoBase (R) project.
|
|
||||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
||||||
* Authors: NocoBase Team.
|
|
||||||
*
|
|
||||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { render } from '@nocobase/test/client';
|
|
||||||
import React from 'react';
|
|
||||||
import { SettingsMenu } from '../CurrentUser';
|
|
||||||
import { useCurrentUserSettingsMenu } from '../CurrentUserSettingsMenuProvider';
|
|
||||||
|
|
||||||
const AppContextProvider = (props) => {
|
|
||||||
return <div></div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: AppContextProvider 没有提供足够的上下文环境
|
|
||||||
describe.skip('CurrentUserSettingsMenuProvider', () => {
|
|
||||||
const wrapper = ({ children }) => {
|
|
||||||
return (
|
|
||||||
<AppContextProvider>
|
|
||||||
<SettingsMenu />
|
|
||||||
{children}
|
|
||||||
</AppContextProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TestComponent = () => {
|
|
||||||
const { getMenuItems } = useCurrentUserSettingsMenu();
|
|
||||||
getMenuItems();
|
|
||||||
return <div>Test</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should throw error when CurrentUserSettingsMenuProvider is not provided', () => {
|
|
||||||
expect(() => {
|
|
||||||
render(<TestComponent />);
|
|
||||||
}).toThrowErrorMatchingInlineSnapshot(
|
|
||||||
'"CurrentUser: You should use `CurrentUserSettingsMenuProvider` in the root of your app."',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw error when providing context', () => {
|
|
||||||
expect(() => {
|
|
||||||
render(<TestComponent />, { wrapper });
|
|
||||||
}).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: result.current 是 null,会报错,暂时不知道哪里出了问题
|
|
||||||
// it.skip('add menu item', () => {
|
|
||||||
// const { result } = renderHook(() => useCurrentUserSettingsMenu(), {
|
|
||||||
// wrapper,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// expect(result.current.getMenuItems()).not.toHaveLength(0);
|
|
||||||
// });
|
|
||||||
});
|
|
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useCurrentRoles, useAPIClient, SchemaSettingsItem, SelectWithTitle } from '@nocobase/client';
|
||||||
|
|
||||||
|
export const SwitchRole = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const roles = useCurrentRoles();
|
||||||
|
if (roles.length <= 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SchemaSettingsItem eventKey="SwitchRole" title="SwitchRole">
|
||||||
|
<SelectWithTitle
|
||||||
|
title={t('Switch role')}
|
||||||
|
fieldNames={{
|
||||||
|
label: 'title',
|
||||||
|
value: 'name',
|
||||||
|
}}
|
||||||
|
options={roles}
|
||||||
|
defaultValue={api.auth.role}
|
||||||
|
onChange={async (roleName) => {
|
||||||
|
api.auth.setRole(roleName);
|
||||||
|
await api.resource('users').setDefaultRole({ values: { roleName } });
|
||||||
|
location.reload();
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SchemaSettingsItem>
|
||||||
|
);
|
||||||
|
};
|
@ -9,9 +9,10 @@
|
|||||||
|
|
||||||
import { Plugin, lazy } from '@nocobase/client';
|
import { Plugin, lazy } from '@nocobase/client';
|
||||||
import { ACLSettingsUI } from './ACLSettingsUI';
|
import { ACLSettingsUI } from './ACLSettingsUI';
|
||||||
// import { RolesManagement } from './RolesManagement';
|
|
||||||
const { RolesManagement } = lazy(() => import('./RolesManagement'), 'RolesManagement');
|
|
||||||
import { RolesManager } from './roles-manager';
|
import { RolesManager } from './roles-manager';
|
||||||
|
import { SwitchRole } from './SwitchRole';
|
||||||
|
|
||||||
|
const { RolesManagement } = lazy(() => import('./RolesManagement'), 'RolesManagement');
|
||||||
|
|
||||||
export class PluginACLClient extends Plugin {
|
export class PluginACLClient extends Plugin {
|
||||||
rolesManager = new RolesManager();
|
rolesManager = new RolesManager();
|
||||||
@ -25,6 +26,18 @@ export class PluginACLClient extends Plugin {
|
|||||||
aclSnippet: 'pm.acl.roles',
|
aclSnippet: 'pm.acl.roles',
|
||||||
sort: 3,
|
sort: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 个人中心注册 切换角色
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'divider_switchRole',
|
||||||
|
type: 'divider',
|
||||||
|
sort: 200,
|
||||||
|
});
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'switchRole',
|
||||||
|
Component: SwitchRole,
|
||||||
|
sort: 300,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useAPIClient, SchemaSettingsItem } from '@nocobase/client';
|
||||||
|
|
||||||
|
export const ClearCache = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const api = useAPIClient();
|
||||||
|
return (
|
||||||
|
<SchemaSettingsItem
|
||||||
|
eventKey="cache"
|
||||||
|
title="cache"
|
||||||
|
onClick={async () => {
|
||||||
|
await api.resource('app').clearCache();
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Clear cache')}
|
||||||
|
</SchemaSettingsItem>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { App } from 'antd';
|
||||||
|
import { useAPIClient, SchemaSettingsItem } from '@nocobase/client';
|
||||||
|
|
||||||
|
export const RestartApplication = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { modal } = App.useApp();
|
||||||
|
return (
|
||||||
|
<SchemaSettingsItem
|
||||||
|
eventKey="restartApplication"
|
||||||
|
title="restartApplication"
|
||||||
|
onClick={async () => {
|
||||||
|
modal.confirm({
|
||||||
|
title: t('Restart application'),
|
||||||
|
// content: t('The will interrupt service, it may take a few seconds to restart. Are you sure to continue?'),
|
||||||
|
okText: t('Restart'),
|
||||||
|
okButtonProps: {
|
||||||
|
danger: true,
|
||||||
|
},
|
||||||
|
onOk: async () => {
|
||||||
|
await api.resource('app').restart();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Restart application')}
|
||||||
|
</SchemaSettingsItem>
|
||||||
|
);
|
||||||
|
};
|
@ -7,25 +7,48 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Plugin } from '@nocobase/client';
|
import { Plugin, useACLRoleContext } from '@nocobase/client';
|
||||||
|
import ignore from 'ignore';
|
||||||
import { DesktopRoutesManager } from './DesktopRoutesManager';
|
import { DesktopRoutesManager } from './DesktopRoutesManager';
|
||||||
import { lang as t } from './locale';
|
import { lang as t } from './locale';
|
||||||
import { MobileRoutesManager } from './MobileRoutesManager';
|
import { MobileRoutesManager } from './MobileRoutesManager';
|
||||||
|
import { ClearCache } from './ClearCache';
|
||||||
|
import { RestartApplication } from './RestartApplication';
|
||||||
|
|
||||||
class PluginClient extends Plugin {
|
class PluginClient extends Plugin {
|
||||||
async load() {
|
async load() {
|
||||||
this.app.pluginSettingsManager.add('routes', {
|
this.app.pluginSettingsManager.add('routes', {
|
||||||
title: t('Routes'),
|
title: t('Routes'),
|
||||||
icon: 'ApartmentOutlined',
|
icon: 'ApartmentOutlined',
|
||||||
aclSnippet: 'pm.routes',
|
aclSnippet: 'ui.*',
|
||||||
});
|
});
|
||||||
this.app.pluginSettingsManager.add(`routes.desktop`, {
|
this.app.pluginSettingsManager.add(`routes.desktop`, {
|
||||||
title: t('Desktop routes'),
|
title: t('Desktop routes'),
|
||||||
Component: DesktopRoutesManager,
|
Component: DesktopRoutesManager,
|
||||||
aclSnippet: 'pm.routes.desktop',
|
aclSnippet: 'ui.*',
|
||||||
sort: 1,
|
sort: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 个人中心注册
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'divider4',
|
||||||
|
sort: 499,
|
||||||
|
type: 'divider',
|
||||||
|
aclSnippet: 'app',
|
||||||
|
});
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'cache',
|
||||||
|
sort: 500,
|
||||||
|
Component: ClearCache,
|
||||||
|
aclSnippet: 'app',
|
||||||
|
});
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'restartApplication',
|
||||||
|
Component: RestartApplication,
|
||||||
|
sort: 510,
|
||||||
|
aclSnippet: 'app',
|
||||||
|
});
|
||||||
|
|
||||||
const mobilePlugin: any = this.app.pluginManager.get('@nocobase/plugin-mobile');
|
const mobilePlugin: any = this.app.pluginManager.get('@nocobase/plugin-mobile');
|
||||||
|
|
||||||
if (mobilePlugin?.options?.enabled) {
|
if (mobilePlugin?.options?.enabled) {
|
||||||
|
@ -7,9 +7,14 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { css, cx, SettingsMenu, SortableItem, useDesigner } from '@nocobase/client';
|
import { css, cx, useSchemaSettingsRender, SortableItem, useDesigner } from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SettingsDesigner } from './Settings.Designer';
|
import { SettingsDesigner } from './Settings.Designer';
|
||||||
|
|
||||||
|
export function UserCenter() {
|
||||||
|
const { render } = useSchemaSettingsRender('userCenterSettings');
|
||||||
|
return <div style={{ display: 'inline-block' }}>{render({ mode: 'inline', style: { width: '100%' } })}</div>;
|
||||||
|
}
|
||||||
export const InternalSettings = () => {
|
export const InternalSettings = () => {
|
||||||
const Designer = useDesigner();
|
const Designer = useDesigner();
|
||||||
return (
|
return (
|
||||||
@ -22,7 +27,7 @@ export const InternalSettings = () => {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Designer />
|
<Designer />
|
||||||
<SettingsMenu redirectUrl="/mobile" />
|
<UserCenter />
|
||||||
</SortableItem>
|
</SortableItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -7,9 +7,14 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { cx, SettingsMenu, SortableItem, useDesigner, useToken } from '@nocobase/client';
|
import { cx, SortableItem, useDesigner, useToken, useSchemaSettingsRender } from '@nocobase/client';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
export function UserCenter() {
|
||||||
|
const { render } = useSchemaSettingsRender('userCenterSettings');
|
||||||
|
return <div style={{ display: 'inline-block', width: '100%' }}>{render({ mode: 'inline' })}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
export const InternalSettings = () => {
|
export const InternalSettings = () => {
|
||||||
const Designer = useDesigner();
|
const Designer = useDesigner();
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
@ -22,7 +27,7 @@ export const InternalSettings = () => {
|
|||||||
return (
|
return (
|
||||||
<SortableItem className={cx('nb-mobile-setting')} style={style}>
|
<SortableItem className={cx('nb-mobile-setting')} style={style}>
|
||||||
<Designer />
|
<Designer />
|
||||||
<SettingsMenu />
|
<UserCenter />
|
||||||
</SortableItem>
|
</SortableItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SchemaSettingsSelectItem } from '@nocobase/client';
|
||||||
|
import { error } from '@nocobase/utils/client';
|
||||||
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
import { useThemeId } from '../components/InitializeTheme';
|
||||||
|
import { useThemeListContext } from '../components/ThemeListProvider';
|
||||||
|
import { useTranslation } from '../locale';
|
||||||
|
import { useUpdateThemeSettings } from '../hooks/useUpdateThemeSettings';
|
||||||
|
|
||||||
|
export const ThemeSettings = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { run, error: err, data } = useThemeListContext();
|
||||||
|
const { updateUserThemeSettings } = useUpdateThemeSettings();
|
||||||
|
const { currentThemeId } = useThemeId();
|
||||||
|
const options = useMemo(() => {
|
||||||
|
return data
|
||||||
|
?.filter((item) => item.optional)
|
||||||
|
.map((item) => {
|
||||||
|
return {
|
||||||
|
label: t(item.config.name),
|
||||||
|
value: item.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [data, t]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SchemaSettingsSelectItem
|
||||||
|
title={t('Theme')}
|
||||||
|
options={options}
|
||||||
|
value={currentThemeId}
|
||||||
|
onChange={(value) => {
|
||||||
|
updateUserThemeSettings(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -7,7 +7,14 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Plugin, createStyles, defaultTheme, useCurrentUserSettingsMenu, useGlobalTheme } from '@nocobase/client';
|
import {
|
||||||
|
Plugin,
|
||||||
|
createStyles,
|
||||||
|
defaultTheme,
|
||||||
|
useCurrentUserSettingsMenu,
|
||||||
|
useGlobalTheme,
|
||||||
|
useACLContext,
|
||||||
|
} from '@nocobase/client';
|
||||||
import { ConfigProvider } from 'antd';
|
import { ConfigProvider } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
@ -23,8 +30,8 @@ const ThemeList = lazy(() => import('./components/ThemeList'));
|
|||||||
const { ThemeListProvider } = lazy(() => import('./components/ThemeListProvider'), 'ThemeListProvider');
|
const { ThemeListProvider } = lazy(() => import('./components/ThemeListProvider'), 'ThemeListProvider');
|
||||||
const CustomTheme = lazy(() => import('./components/theme-editor'));
|
const CustomTheme = lazy(() => import('./components/theme-editor'));
|
||||||
|
|
||||||
import { useThemeSettings } from './hooks/useThemeSettings';
|
|
||||||
import { NAMESPACE } from './locale';
|
import { NAMESPACE } from './locale';
|
||||||
|
import { ThemeSettings } from './components/ThemeSettings';
|
||||||
|
|
||||||
const useStyles = createStyles(({ css, token }) => {
|
const useStyles = createStyles(({ css, token }) => {
|
||||||
return {
|
return {
|
||||||
@ -45,17 +52,10 @@ const useStyles = createStyles(({ css, token }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const CustomThemeProvider = React.memo((props) => {
|
const CustomThemeProvider = React.memo((props) => {
|
||||||
const { addMenuItem } = useCurrentUserSettingsMenu();
|
|
||||||
const themeItem = useThemeSettings();
|
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const { theme, setTheme } = useGlobalTheme();
|
const { theme, setTheme } = useGlobalTheme();
|
||||||
const { styles } = useStyles();
|
const { styles } = useStyles();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// 在页面右上角中添加一个 Theme 菜单项
|
|
||||||
addMenuItem(themeItem, { before: 'divider_3' });
|
|
||||||
}, [addMenuItem, themeItem]);
|
|
||||||
|
|
||||||
const contentStyle = useMemo(() => {
|
const contentStyle = useMemo(() => {
|
||||||
return open
|
return open
|
||||||
? { transform: 'rotate(0)', flexGrow: 1, width: 0, height: '100%' }
|
? { transform: 'rotate(0)', flexGrow: 1, width: 0, height: '100%' }
|
||||||
@ -100,6 +100,12 @@ export class PluginThemeEditorClient extends Plugin {
|
|||||||
Component: ThemeList,
|
Component: ThemeList,
|
||||||
aclSnippet: 'pm.theme-editor.themes',
|
aclSnippet: 'pm.theme-editor.themes',
|
||||||
});
|
});
|
||||||
|
// 个人中心注册 Theme 菜单项
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'theme',
|
||||||
|
sort: 310,
|
||||||
|
Component: ThemeSettings,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ export class PluginThemeEditorServer extends Plugin {
|
|||||||
|
|
||||||
this.app.acl.allow('themeConfig', 'list', 'public');
|
this.app.acl.allow('themeConfig', 'list', 'public');
|
||||||
this.app.acl.registerSnippet({
|
this.app.acl.registerSnippet({
|
||||||
name: `pm.${this.name}.themeConfig`,
|
name: `pm.${this.name}.themes`,
|
||||||
actions: ['themeConfig:*'],
|
actions: ['themeConfig:*'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,18 +9,21 @@
|
|||||||
|
|
||||||
import { ISchema, useForm } from '@formily/react';
|
import { ISchema, useForm } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { MenuProps } from 'antd';
|
|
||||||
import React, { useContext, useMemo, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import React, { useContext, useEffect, useMemo, useState, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
ActionContextProvider,
|
ActionContextProvider,
|
||||||
DropdownVisibleContext,
|
DropdownVisibleContext,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
useActionContext,
|
useActionContext,
|
||||||
useSystemSettings,
|
useSystemSettings,
|
||||||
} from '../';
|
zIndexContext,
|
||||||
import { useAPIClient } from '../api-client';
|
useZIndexContext,
|
||||||
|
SchemaComponentContext,
|
||||||
|
useAPIClient,
|
||||||
|
SchemaSettingsItem,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
|
||||||
const useCloseAction = () => {
|
const useCloseAction = () => {
|
||||||
const { setVisible } = useActionContext();
|
const { setVisible } = useActionContext();
|
||||||
@ -133,36 +136,44 @@ const schema: ISchema = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useChangePassword = () => {
|
export const ChangePassword = () => {
|
||||||
const ctx = useContext(DropdownVisibleContext);
|
const ctx = useContext(DropdownVisibleContext);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data } = useSystemSettings() || {};
|
const { data } = useSystemSettings() || {};
|
||||||
const { enableChangePassword } = data?.data || {};
|
const { enableChangePassword } = data?.data || {};
|
||||||
|
const parentZIndex = useZIndexContext();
|
||||||
|
const zIndex = parentZIndex + 10;
|
||||||
|
|
||||||
|
// 避免重复渲染的 click 处理
|
||||||
|
const handleClick = useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
ctx?.setVisible?.(false);
|
||||||
|
setVisible((prev) => (prev ? prev : true)); // 只有 `visible` 变化时才触发更新
|
||||||
|
},
|
||||||
|
[ctx],
|
||||||
|
);
|
||||||
|
|
||||||
|
const schemaComponent = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<SchemaComponentContext.Provider value={{ designable: false }}>
|
||||||
|
<SchemaComponent scope={{ useCloseAction, useSaveCurrentUserValues }} schema={schema} />
|
||||||
|
</SchemaComponentContext.Provider>
|
||||||
|
);
|
||||||
|
}, [zIndex]);
|
||||||
|
|
||||||
const result = useMemo<MenuProps['items'][0]>(() => {
|
|
||||||
return {
|
|
||||||
key: 'password',
|
|
||||||
eventKey: 'ChangePassword',
|
|
||||||
onClick: () => {
|
|
||||||
setVisible(true);
|
|
||||||
ctx?.setVisible(false);
|
|
||||||
},
|
|
||||||
label: (
|
|
||||||
<>
|
|
||||||
{t('Change password')}
|
|
||||||
<ActionContextProvider value={{ visible, setVisible }}>
|
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
|
||||||
<SchemaComponent scope={{ useCloseAction, useSaveCurrentUserValues }} schema={schema} />
|
|
||||||
</div>
|
|
||||||
</ActionContextProvider>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}, [visible]);
|
|
||||||
if (enableChangePassword === false) {
|
if (enableChangePassword === false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
return result;
|
<zIndexContext.Provider value={zIndex}>
|
||||||
|
<SchemaSettingsItem eventKey="changePassword" title="changePassword">
|
||||||
|
<div onClick={handleClick}>{t('Change password')}</div>
|
||||||
|
</SchemaSettingsItem>
|
||||||
|
<ActionContextProvider value={{ visible, setVisible }}>
|
||||||
|
{visible && <div onClick={(e) => e.stopPropagation()}>{schemaComponent}</div>}
|
||||||
|
</ActionContextProvider>
|
||||||
|
</zIndexContext.Provider>
|
||||||
|
);
|
||||||
};
|
};
|
@ -9,8 +9,7 @@
|
|||||||
|
|
||||||
import { useField, useFieldSchema, useForm } from '@formily/react';
|
import { useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { MenuProps } from 'antd';
|
import React, { useContext, useEffect, useMemo, useState, useCallback } from 'react';
|
||||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ActionContextProvider,
|
ActionContextProvider,
|
||||||
@ -23,8 +22,11 @@ import {
|
|||||||
useCollectionManager,
|
useCollectionManager,
|
||||||
useCurrentUserContext,
|
useCurrentUserContext,
|
||||||
useSystemSettings,
|
useSystemSettings,
|
||||||
} from '../';
|
zIndexContext,
|
||||||
import { useAPIClient } from '../api-client';
|
useZIndexContext,
|
||||||
|
useAPIClient,
|
||||||
|
SchemaSettingsItem,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
|
||||||
const useUpdateProfileActionProps = () => {
|
const useUpdateProfileActionProps = () => {
|
||||||
const ctx = useCurrentUserContext();
|
const ctx = useCurrentUserContext();
|
||||||
@ -70,9 +72,9 @@ const useUpdateProfileActionProps = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useEditProfileFormBlockDecoratorProps = () => {
|
const useEditProfileFormBlockDecoratorProps = () => {
|
||||||
const { data } = useCurrentUserContext();
|
const { data } = useCurrentUserContext() || {};
|
||||||
return {
|
return {
|
||||||
filterByTk: data.data?.id,
|
filterByTk: data?.data?.id,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -116,57 +118,61 @@ const ProfileEditForm = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useEditProfile = () => {
|
export const EditProfile = () => {
|
||||||
const ctx = useContext(DropdownVisibleContext);
|
const ctx = useContext(DropdownVisibleContext);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data } = useSystemSettings() || {};
|
const { data } = useSystemSettings() || {};
|
||||||
const { enableEditProfile } = data?.data || {};
|
const { enableEditProfile } = data?.data ?? {};
|
||||||
const result = useMemo<MenuProps['items'][0]>(() => {
|
const parentZIndex = useZIndexContext();
|
||||||
return {
|
const zIndex = parentZIndex + 10;
|
||||||
key: 'profile',
|
|
||||||
eventKey: 'EditProfile',
|
|
||||||
onClick: () => {
|
|
||||||
ctx?.setVisible(false);
|
|
||||||
setVisible(true);
|
|
||||||
},
|
|
||||||
label: (
|
|
||||||
<div>
|
|
||||||
{t('Edit profile')}
|
|
||||||
<ActionContextProvider value={{ visible, setVisible }}>
|
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
|
||||||
<SchemaComponent
|
|
||||||
components={{ ProfileEditForm }}
|
|
||||||
schema={{
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
[uid()]: {
|
|
||||||
'x-component': 'Action.Drawer',
|
|
||||||
'x-component-props': {
|
|
||||||
zIndex: 2000,
|
|
||||||
},
|
|
||||||
type: 'void',
|
|
||||||
title: '{{t("Edit profile")}}',
|
|
||||||
properties: {
|
|
||||||
form: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'ProfileEditForm',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ActionContextProvider>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
|
// 避免重复渲染的 click 处理
|
||||||
|
const handleClick = useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
ctx?.setVisible?.(false);
|
||||||
|
setVisible((prev) => (prev ? prev : true)); // 只有 `visible` 变化时才触发更新
|
||||||
|
},
|
||||||
|
[ctx],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 避免 `SchemaComponent` 结构重新创建
|
||||||
|
const schemaComponent = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
components={{ ProfileEditForm }}
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-component-props': { zIndex },
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Edit profile")}}',
|
||||||
|
properties: {
|
||||||
|
form: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ProfileEditForm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [zIndex]);
|
||||||
if (enableEditProfile === false) {
|
if (enableEditProfile === false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
return result;
|
<zIndexContext.Provider value={zIndex}>
|
||||||
|
<SchemaSettingsItem eventKey="EditProfile" title="EditProfile">
|
||||||
|
<div onClick={handleClick}>{t('Edit profile')}</div>
|
||||||
|
</SchemaSettingsItem>
|
||||||
|
<ActionContextProvider value={{ visible, setVisible }}>
|
||||||
|
{visible && <div onClick={(e) => e.stopPropagation()}>{schemaComponent}</div>}
|
||||||
|
</ActionContextProvider>
|
||||||
|
</zIndexContext.Provider>
|
||||||
|
);
|
||||||
};
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { SchemaSettingsItem, useToken, useCurrentUserContext, SchemaSettings } from '@nocobase/client';
|
||||||
|
|
||||||
|
export const NickName = () => {
|
||||||
|
const { data } = useCurrentUserContext();
|
||||||
|
const { token } = useToken();
|
||||||
|
return (
|
||||||
|
<SchemaSettingsItem disabled={true} eventKey="nickname" title="nickname">
|
||||||
|
<span aria-disabled="false" style={{ cursor: 'text', color: token.colorTextDescription }}>
|
||||||
|
{data?.data?.nickname || data?.data?.username || data?.data?.email}
|
||||||
|
</span>
|
||||||
|
</SchemaSettingsItem>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { SchemaSettingsItem, useNavigateNoUpdate, useAPIClient } from '@nocobase/client';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const SignOut = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigateNoUpdate();
|
||||||
|
const api = useAPIClient();
|
||||||
|
return (
|
||||||
|
<SchemaSettingsItem
|
||||||
|
title="signOut"
|
||||||
|
eventKey="signOut"
|
||||||
|
onClick={async () => {
|
||||||
|
const { data } = await api.auth.signOut();
|
||||||
|
if (data?.data?.redirect) {
|
||||||
|
window.location.href = data.data.redirect;
|
||||||
|
} else {
|
||||||
|
navigate(`/signin?redirect=${encodeURIComponent('')}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Sign out')}
|
||||||
|
</SchemaSettingsItem>
|
||||||
|
);
|
||||||
|
};
|
@ -96,37 +96,6 @@ const ProfileEditForm = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const EditProfile = ({ visible, setVisible }) => {
|
|
||||||
return (
|
|
||||||
<ActionContextProvider value={{ visible, setVisible }}>
|
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
|
||||||
<SchemaComponent
|
|
||||||
components={{ ProfileEditForm }}
|
|
||||||
schema={{
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
[uid()]: {
|
|
||||||
'x-component': 'Action.Drawer',
|
|
||||||
'x-component-props': {
|
|
||||||
// zIndex: 10000,
|
|
||||||
},
|
|
||||||
type: 'void',
|
|
||||||
title: '{{t("Edit profile")}}',
|
|
||||||
properties: {
|
|
||||||
form: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'ProfileEditForm',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ActionContextProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useEditProfile = () => {
|
export const useEditProfile = () => {
|
||||||
const ctx = useContext(DropdownVisibleContext);
|
const ctx = useContext(DropdownVisibleContext);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
@ -11,10 +11,14 @@ import { Plugin } from '@nocobase/client';
|
|||||||
import { tval } from '@nocobase/utils/client';
|
import { tval } from '@nocobase/utils/client';
|
||||||
import ACLPlugin from '@nocobase/plugin-acl/client';
|
import ACLPlugin from '@nocobase/plugin-acl/client';
|
||||||
import { lazy } from '@nocobase/client';
|
import { lazy } from '@nocobase/client';
|
||||||
|
import { ChangePassword } from './ChangePassword';
|
||||||
|
import { EditProfile } from './EditProfile';
|
||||||
|
import { NickName } from './NickName';
|
||||||
|
import { SignOut } from './SignOut';
|
||||||
|
|
||||||
const { UsersProvider } = lazy(() => import('./UsersProvider'), 'UsersProvider');
|
const { UsersProvider } = lazy(() => import('./UsersProvider'), 'UsersProvider');
|
||||||
const { UsersManagement } = lazy(() => import('./UsersManagement'), 'UsersManagement');
|
const { UsersManagement } = lazy(() => import('./UsersManagement'), 'UsersManagement');
|
||||||
const { RoleUsersManager } = lazy(() => import('./RoleUsersManager'), 'RoleUsersManager');
|
const { RoleUsersManager } = lazy(() => import('./RoleUsersManager'), 'RoleUsersManager');
|
||||||
|
|
||||||
class PluginUsersClient extends Plugin {
|
class PluginUsersClient extends Plugin {
|
||||||
async load() {
|
async load() {
|
||||||
this.app.pluginSettingsManager.add('users-permissions', {
|
this.app.pluginSettingsManager.add('users-permissions', {
|
||||||
@ -33,6 +37,37 @@ class PluginUsersClient extends Plugin {
|
|||||||
title: tval('Users'),
|
title: tval('Users'),
|
||||||
Component: RoleUsersManager,
|
Component: RoleUsersManager,
|
||||||
});
|
});
|
||||||
|
// 个人中心注册 注册设置项
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'nickName',
|
||||||
|
Component: NickName,
|
||||||
|
sort: 0,
|
||||||
|
});
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'divider1',
|
||||||
|
type: 'divider',
|
||||||
|
sort: 10,
|
||||||
|
});
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'editProfile',
|
||||||
|
Component: EditProfile,
|
||||||
|
sort: 50,
|
||||||
|
});
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'changePassword',
|
||||||
|
Component: ChangePassword,
|
||||||
|
sort: 100,
|
||||||
|
});
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'divider_signOut',
|
||||||
|
type: 'divider',
|
||||||
|
sort: 900,
|
||||||
|
});
|
||||||
|
this.app.addUserCenterSettingsItem({
|
||||||
|
name: 'signOut',
|
||||||
|
Component: SignOut,
|
||||||
|
sort: 1000,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user