如何根据用户信息显示不同的侧边栏?登录后。ReactJS
How can I show a different sidebar based on user information? After logged in. ReactJS
我有一个反应 js 应用程序,它使用 azure 活动目录进行身份验证,然后它显示这个菜单:
但是我希望在登录后,根据从 Azure AD 收到的信息,如角色或组,然后显示具有不同选项的不同侧边栏,这样我就可以拥有具有不同用户组的相同应用程序和菜单将取决于角色或组。
这是我的应用结构:
这里是相关文件:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import DashApp from './dashApp';
import registerServiceWorker from './registerServiceWorker';
import 'antd/dist/antd.css';
import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';
const DO_NOT_LOGIN = false;
runWithAdal(authContext, () => {
ReactDOM.render(<DashApp />, document.getElementById('root'));
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./dashApp.js', () => {
const NextApp = require('./dashApp').default;
ReactDOM.render(<NextApp />, document.getElementById('root'));
});
}
},DO_NOT_LOGIN);
registerServiceWorker();
Sidebebar.js
import React, { Component } from "react";
import { connect } from "react-redux";
import clone from "clone";
import { Link } from "react-router-dom";
import { Layout } from "antd";
import options from "./options";
import Scrollbars from "../../components/utility/customScrollBar.js";
import Menu from "../../components/uielements/menu";
import IntlMessages from "../../components/utility/intlMessages";
import SidebarWrapper from "./sidebar.style";
import appActions from "../../redux/app/actions";
import Logo from "../../components/utility/logo";
import themes from "../../settings/themes";
import { themeConfig } from "../../settings";
const SubMenu = Menu.SubMenu;
const { Sider } = Layout;
const {
toggleOpenDrawer,
changeOpenKeys,
changeCurrent,
toggleCollapsed
} = appActions;
const stripTrailingSlash = str => {
if (str.substr(-1) === "/") {
return str.substr(0, str.length - 1);
}
return str;
};
class Sidebar extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.onOpenChange = this.onOpenChange.bind(this);
}
handleClick(e) {
this.props.changeCurrent([e.key]);
if (this.props.app.view === "MobileView") {
setTimeout(() => {
this.props.toggleCollapsed();
this.props.toggleOpenDrawer();
}, 100);
}
}
onOpenChange(newOpenKeys) {
const { app, changeOpenKeys } = this.props;
const latestOpenKey = newOpenKeys.find(
key => !(app.openKeys.indexOf(key) > -1)
);
const latestCloseKey = app.openKeys.find(
key => !(newOpenKeys.indexOf(key) > -1)
);
let nextOpenKeys = [];
if (latestOpenKey) {
nextOpenKeys = this.getAncestorKeys(latestOpenKey).concat(latestOpenKey);
}
if (latestCloseKey) {
nextOpenKeys = this.getAncestorKeys(latestCloseKey);
}
changeOpenKeys(nextOpenKeys);
}
getAncestorKeys = key => {
const map = {
sub3: ["sub2"]
};
return map[key] || [];
};
getMenuItem = ({ singleOption, submenuStyle, submenuColor }) => {
const { key, label, leftIcon, children } = singleOption;
const url = stripTrailingSlash(this.props.url);
if (children) {
return (
<SubMenu
key={key}
title={
<span className="isoMenuHolder" style={submenuColor}>
<i className={leftIcon} />
<span className="nav-text">
<IntlMessages id={label} />
</span>
</span>
}
>
{children.map(child => {
const linkTo = child.withoutDashboard
? `/${child.key}`
: `${url}/${child.key}`;
return (
<Menu.Item style={submenuStyle} key={child.key}>
<Link style={submenuColor} to={linkTo}>
<IntlMessages id={child.label} />
</Link>
</Menu.Item>
);
})}
</SubMenu>
);
}
return (
<Menu.Item key={key}>
<Link to={`${url}/${key}`}>
<span className="isoMenuHolder" style={submenuColor}>
<i className={leftIcon} />
<span className="nav-text">
<IntlMessages id={label} />
</span>
</span>
</Link>
</Menu.Item>
);
};
render() {
const { app, toggleOpenDrawer, height } = this.props;
const collapsed = clone(app.collapsed) && !clone(app.openDrawer);
const { openDrawer } = app;
const mode = collapsed === true ? "vertical" : "inline";
const onMouseEnter = event => {
if (openDrawer === false) {
toggleOpenDrawer();
}
return;
};
const onMouseLeave = () => {
if (openDrawer === true) {
toggleOpenDrawer();
}
return;
};
const customizedTheme = themes[themeConfig.theme];
const styling = {
backgroundColor: customizedTheme.backgroundColor
};
const submenuStyle = {
backgroundColor: "rgba(0,0,0,0.3)",
color: customizedTheme.textColor
};
const submenuColor = {
color: customizedTheme.textColor
};
return (
<SidebarWrapper>
<Sider
trigger={null}
collapsible={true}
collapsed={collapsed}
width="240"
className="isomorphicSidebar"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={styling}
>
<Logo collapsed={collapsed} />
<Scrollbars style={{ height: height - 70 }}>
<Menu
onClick={this.handleClick}
theme="dark"
className="isoDashboardMenu"
mode={mode}
openKeys={collapsed ? [] : app.openKeys}
selectedKeys={app.current}
onOpenChange={this.onOpenChange}
>
{options.map(singleOption =>
this.getMenuItem({ submenuStyle, submenuColor, singleOption })
)}
</Menu>
</Scrollbars>
</Sider>
</SidebarWrapper>
);
}
}
export default connect(
state => ({
app: state.App.toJS(),
height: state.App.toJS().height
}),
{ toggleOpenDrawer, changeOpenKeys, changeCurrent, toggleCollapsed }
)(Sidebar);
dashapp.js
import React from "react";
import { Provider } from "react-redux";
import { store, history } from "./redux/store";
import PublicRoutes from "./router";
import { ThemeProvider } from "styled-components";
import { LocaleProvider } from "antd";
import { IntlProvider } from "react-intl";
import themes from "./settings/themes";
import AppLocale from "./languageProvider";
import config, {
getCurrentLanguage
} from "./containers/LanguageSwitcher/config";
import { themeConfig } from "./settings";
import DashAppHolder from "./dashAppStyle";
import Boot from "./redux/boot";
const currentAppLocale =
AppLocale[getCurrentLanguage(config.defaultLanguage || "english").locale];
const DashApp = () => (
<LocaleProvider locale={currentAppLocale.antd}>
<IntlProvider
locale={currentAppLocale.locale}
messages={currentAppLocale.messages}
>
<ThemeProvider theme={themes[themeConfig.theme]}>
<DashAppHolder>
<Provider store={store}>
<PublicRoutes history={history} />
</Provider>
</DashAppHolder>
</ThemeProvider>
</IntlProvider>
</LocaleProvider>
);
Boot()
.then(() => DashApp())
.catch(error => console.error(error));
export default DashApp;
export { AppLocale };
问题:
如何修改此代码以根据登录用户呈现不同的边栏?
第一步高度依赖于您的身份验证配置方式。使用 azure 登录后,您需要将检索到的用户配置文件存储在 redux 数据存储中。
如果您将 react redux 与 adal 一起使用,您应该会发送一个登录操作。
此操作成功后,在您的登录减速器中,从检索到的信息中选择您需要的内容。
以下代码只是实施此类减速器后可能剩下的示例。
AD 肯定会给你一个组 ID,而不是本例中显示的 'admin' 角色,只需相应地修改检查即可。
您可能需要在 AAD 中的应用程序清单中将 groupMembershipClaims
设置为 "SecurityGroup"
或 "All"
,以便在身份验证响应中添加这条信息。
case Action.LOGIN_SUCCESS:
return {
...state,
username: action.username,
isAdmin: action.role === 'admin'
}
您可能需要自定义 AD 从 AD 仪表板发送给您的信息。
剩下的都是微不足道的:
将商店连接到需要用户权限的组件
connect(state => ({
user: state.loginReducer,
}))
在您的组件内有条件地自定义渲染
render() {
const { user } = this.props;
return (
<div className={classNames.navbar}>
{ user.isAdmin &&
<Link to="adminpanel" label="Admin Panel" />
}
<Link to="about" label="About" />
</div>
)
}
顺便说一句,如果你需要处理动态语言切换,你应该在 redux 存储中设置语言环境。
在提供的代码中,我没有找到确定用户是否已通过身份验证的方法。我认为这是这里的重点。
最简单的方法是检查 cookie。这当然取决于您的身份验证是如何工作的。您需要有一个功能来检查身份验证 cookie。像这样的东西。我正在使用 universal-cookie
包,看起来你的应用程序是同构的,所以你需要在服务器上传递 ssrCookie
,你将从 res.headers.cookies
获得它。如果该应用程序仅在客户端,您可以删除 ssrCookie
:
import Cookies from 'universal-cookie'
const authCookieName = 'awesome_app__token'
function getCookies(isServer, ssrCookie) {
if (isServer) {
return new Cookies(ssrCookie || '')
} else {
return new Cookies()
}
}
export function isLoggedIn(isServer, ssrCookie) {
return !!getToken(getCookies(isServer, ssrCookie), ssrCookie)
}
function getToken(isServer, ssrCookie) {
return getCookies(isServer, ssrCookie).get(authCookieName)
}
所以,现在,在你的反应代码中,你检查用户是否登录:
import { isLoggedIn } from './thePathToFileWhereThisFunctionsWasSet'
class Sidebar extends Component {
render() {
return (
{isLoggedIn() ? (<div>For logged in user</div>) : (<div>For not logged in users</div>)}
)
}
}
在 react-adal
代码中挖掘了一下之后,我发现 logged
状态没有通过子上下文传递。据我所知,获取当前用户的唯一方法是:
import { authContext } from './adalConfig';
authContext.getCachedUser();
这只有在你用 runWithAdal
包装了你的主要组件之后才会这样做。
有了它,您可以创建一个新组件 SidebarOptions
,它将决定要呈现的道具 user
(user = authContext.getCachedUser();
) 的选项。
另一种选择是创建一个 PR 到 react-adal
并确保通过包装组件或所有子组件传递这些值s components (using
react-context`)
此外,正如我所看到的,您正在使用 redux,因此最好通过使用 authContext.getCachedUser()
数据在第一个呈现的组件上分派操作来保存用户信息,并将其设置在您的商店中以便于访问整个应用程序。执行此操作的好地方是 withAdalLoginApi
.
的成功回调
我有一个反应 js 应用程序,它使用 azure 活动目录进行身份验证,然后它显示这个菜单:
但是我希望在登录后,根据从 Azure AD 收到的信息,如角色或组,然后显示具有不同选项的不同侧边栏,这样我就可以拥有具有不同用户组的相同应用程序和菜单将取决于角色或组。
这是我的应用结构:
这里是相关文件:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import DashApp from './dashApp';
import registerServiceWorker from './registerServiceWorker';
import 'antd/dist/antd.css';
import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';
const DO_NOT_LOGIN = false;
runWithAdal(authContext, () => {
ReactDOM.render(<DashApp />, document.getElementById('root'));
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./dashApp.js', () => {
const NextApp = require('./dashApp').default;
ReactDOM.render(<NextApp />, document.getElementById('root'));
});
}
},DO_NOT_LOGIN);
registerServiceWorker();
Sidebebar.js
import React, { Component } from "react";
import { connect } from "react-redux";
import clone from "clone";
import { Link } from "react-router-dom";
import { Layout } from "antd";
import options from "./options";
import Scrollbars from "../../components/utility/customScrollBar.js";
import Menu from "../../components/uielements/menu";
import IntlMessages from "../../components/utility/intlMessages";
import SidebarWrapper from "./sidebar.style";
import appActions from "../../redux/app/actions";
import Logo from "../../components/utility/logo";
import themes from "../../settings/themes";
import { themeConfig } from "../../settings";
const SubMenu = Menu.SubMenu;
const { Sider } = Layout;
const {
toggleOpenDrawer,
changeOpenKeys,
changeCurrent,
toggleCollapsed
} = appActions;
const stripTrailingSlash = str => {
if (str.substr(-1) === "/") {
return str.substr(0, str.length - 1);
}
return str;
};
class Sidebar extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.onOpenChange = this.onOpenChange.bind(this);
}
handleClick(e) {
this.props.changeCurrent([e.key]);
if (this.props.app.view === "MobileView") {
setTimeout(() => {
this.props.toggleCollapsed();
this.props.toggleOpenDrawer();
}, 100);
}
}
onOpenChange(newOpenKeys) {
const { app, changeOpenKeys } = this.props;
const latestOpenKey = newOpenKeys.find(
key => !(app.openKeys.indexOf(key) > -1)
);
const latestCloseKey = app.openKeys.find(
key => !(newOpenKeys.indexOf(key) > -1)
);
let nextOpenKeys = [];
if (latestOpenKey) {
nextOpenKeys = this.getAncestorKeys(latestOpenKey).concat(latestOpenKey);
}
if (latestCloseKey) {
nextOpenKeys = this.getAncestorKeys(latestCloseKey);
}
changeOpenKeys(nextOpenKeys);
}
getAncestorKeys = key => {
const map = {
sub3: ["sub2"]
};
return map[key] || [];
};
getMenuItem = ({ singleOption, submenuStyle, submenuColor }) => {
const { key, label, leftIcon, children } = singleOption;
const url = stripTrailingSlash(this.props.url);
if (children) {
return (
<SubMenu
key={key}
title={
<span className="isoMenuHolder" style={submenuColor}>
<i className={leftIcon} />
<span className="nav-text">
<IntlMessages id={label} />
</span>
</span>
}
>
{children.map(child => {
const linkTo = child.withoutDashboard
? `/${child.key}`
: `${url}/${child.key}`;
return (
<Menu.Item style={submenuStyle} key={child.key}>
<Link style={submenuColor} to={linkTo}>
<IntlMessages id={child.label} />
</Link>
</Menu.Item>
);
})}
</SubMenu>
);
}
return (
<Menu.Item key={key}>
<Link to={`${url}/${key}`}>
<span className="isoMenuHolder" style={submenuColor}>
<i className={leftIcon} />
<span className="nav-text">
<IntlMessages id={label} />
</span>
</span>
</Link>
</Menu.Item>
);
};
render() {
const { app, toggleOpenDrawer, height } = this.props;
const collapsed = clone(app.collapsed) && !clone(app.openDrawer);
const { openDrawer } = app;
const mode = collapsed === true ? "vertical" : "inline";
const onMouseEnter = event => {
if (openDrawer === false) {
toggleOpenDrawer();
}
return;
};
const onMouseLeave = () => {
if (openDrawer === true) {
toggleOpenDrawer();
}
return;
};
const customizedTheme = themes[themeConfig.theme];
const styling = {
backgroundColor: customizedTheme.backgroundColor
};
const submenuStyle = {
backgroundColor: "rgba(0,0,0,0.3)",
color: customizedTheme.textColor
};
const submenuColor = {
color: customizedTheme.textColor
};
return (
<SidebarWrapper>
<Sider
trigger={null}
collapsible={true}
collapsed={collapsed}
width="240"
className="isomorphicSidebar"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={styling}
>
<Logo collapsed={collapsed} />
<Scrollbars style={{ height: height - 70 }}>
<Menu
onClick={this.handleClick}
theme="dark"
className="isoDashboardMenu"
mode={mode}
openKeys={collapsed ? [] : app.openKeys}
selectedKeys={app.current}
onOpenChange={this.onOpenChange}
>
{options.map(singleOption =>
this.getMenuItem({ submenuStyle, submenuColor, singleOption })
)}
</Menu>
</Scrollbars>
</Sider>
</SidebarWrapper>
);
}
}
export default connect(
state => ({
app: state.App.toJS(),
height: state.App.toJS().height
}),
{ toggleOpenDrawer, changeOpenKeys, changeCurrent, toggleCollapsed }
)(Sidebar);
dashapp.js
import React from "react";
import { Provider } from "react-redux";
import { store, history } from "./redux/store";
import PublicRoutes from "./router";
import { ThemeProvider } from "styled-components";
import { LocaleProvider } from "antd";
import { IntlProvider } from "react-intl";
import themes from "./settings/themes";
import AppLocale from "./languageProvider";
import config, {
getCurrentLanguage
} from "./containers/LanguageSwitcher/config";
import { themeConfig } from "./settings";
import DashAppHolder from "./dashAppStyle";
import Boot from "./redux/boot";
const currentAppLocale =
AppLocale[getCurrentLanguage(config.defaultLanguage || "english").locale];
const DashApp = () => (
<LocaleProvider locale={currentAppLocale.antd}>
<IntlProvider
locale={currentAppLocale.locale}
messages={currentAppLocale.messages}
>
<ThemeProvider theme={themes[themeConfig.theme]}>
<DashAppHolder>
<Provider store={store}>
<PublicRoutes history={history} />
</Provider>
</DashAppHolder>
</ThemeProvider>
</IntlProvider>
</LocaleProvider>
);
Boot()
.then(() => DashApp())
.catch(error => console.error(error));
export default DashApp;
export { AppLocale };
问题:
如何修改此代码以根据登录用户呈现不同的边栏?
第一步高度依赖于您的身份验证配置方式。使用 azure 登录后,您需要将检索到的用户配置文件存储在 redux 数据存储中。
如果您将 react redux 与 adal 一起使用,您应该会发送一个登录操作。 此操作成功后,在您的登录减速器中,从检索到的信息中选择您需要的内容。
以下代码只是实施此类减速器后可能剩下的示例。
AD 肯定会给你一个组 ID,而不是本例中显示的 'admin' 角色,只需相应地修改检查即可。
您可能需要在 AAD 中的应用程序清单中将 groupMembershipClaims
设置为 "SecurityGroup"
或 "All"
,以便在身份验证响应中添加这条信息。
case Action.LOGIN_SUCCESS:
return {
...state,
username: action.username,
isAdmin: action.role === 'admin'
}
您可能需要自定义 AD 从 AD 仪表板发送给您的信息。
剩下的都是微不足道的:
将商店连接到需要用户权限的组件
connect(state => ({ user: state.loginReducer, }))
在您的组件内有条件地自定义渲染
render() { const { user } = this.props; return ( <div className={classNames.navbar}> { user.isAdmin && <Link to="adminpanel" label="Admin Panel" /> } <Link to="about" label="About" /> </div> ) }
顺便说一句,如果你需要处理动态语言切换,你应该在 redux 存储中设置语言环境。
在提供的代码中,我没有找到确定用户是否已通过身份验证的方法。我认为这是这里的重点。
最简单的方法是检查 cookie。这当然取决于您的身份验证是如何工作的。您需要有一个功能来检查身份验证 cookie。像这样的东西。我正在使用 universal-cookie
包,看起来你的应用程序是同构的,所以你需要在服务器上传递 ssrCookie
,你将从 res.headers.cookies
获得它。如果该应用程序仅在客户端,您可以删除 ssrCookie
:
import Cookies from 'universal-cookie'
const authCookieName = 'awesome_app__token'
function getCookies(isServer, ssrCookie) {
if (isServer) {
return new Cookies(ssrCookie || '')
} else {
return new Cookies()
}
}
export function isLoggedIn(isServer, ssrCookie) {
return !!getToken(getCookies(isServer, ssrCookie), ssrCookie)
}
function getToken(isServer, ssrCookie) {
return getCookies(isServer, ssrCookie).get(authCookieName)
}
所以,现在,在你的反应代码中,你检查用户是否登录:
import { isLoggedIn } from './thePathToFileWhereThisFunctionsWasSet'
class Sidebar extends Component {
render() {
return (
{isLoggedIn() ? (<div>For logged in user</div>) : (<div>For not logged in users</div>)}
)
}
}
在 react-adal
代码中挖掘了一下之后,我发现 logged
状态没有通过子上下文传递。据我所知,获取当前用户的唯一方法是:
import { authContext } from './adalConfig';
authContext.getCachedUser();
这只有在你用 runWithAdal
包装了你的主要组件之后才会这样做。
有了它,您可以创建一个新组件 SidebarOptions
,它将决定要呈现的道具 user
(user = authContext.getCachedUser();
) 的选项。
另一种选择是创建一个 PR 到 react-adal
并确保通过包装组件或所有子组件传递这些值s components (using
react-context`)
此外,正如我所看到的,您正在使用 redux,因此最好通过使用 authContext.getCachedUser()
数据在第一个呈现的组件上分派操作来保存用户信息,并将其设置在您的商店中以便于访问整个应用程序。执行此操作的好地方是 withAdalLoginApi
.