React后台管理制作历史导航条
效果图
项目目录
分析
1、在Tabs组件里使用 this.props.history.listen 监听路由变化,路由变化时就添加对应的历史记录
2、在Tabs组件和LeftMenu组件里使用事件订阅进行通信(pubsub-js), 当点击Tabs组件里的标签时,leftMenu跳转到相应的菜单栏
Tabs组件注意点
1、因为Tabs组件的标签是有展开和隐藏功能,所以需要获取到相应盒子的高度,使用refs获取盒子高度时,可以先执行this.setState({})更新一下页面,这样获取的高度才是准确的(或许也需要使用定时器延迟获取高度),在网上查阅资料的时候,查到可以使用Mutation Observer API 用来监视 DOM 变动
2、需要使用监听浏览器窗口的大小: window.addEventListener("resize", feekback, false),Tabs展开的高度是需要动态改变的
3、点击菜单栏和删除历史记录时都需要重新获取相应盒子的高度
4、通过 this.props.history.listen 监听路由改变,需要在组件卸载的时候时候执行解绑(监听窗口大小的的事件也是需要解绑),解绑this.props.history.listen 的方法是:在 componentWillUnmount 生命周期中执行 this.props.history.listen 事件的返回值 ,示例如下:
javascript
import React, { Component } from 'react';
let unListen; // this.props.history.listen 事件的返回值,用于解绑listen事件
class Tabs extends Component {
componentDidMount () {
// 监听路由变化
unListen = this.props.history.listen((route) => {
});
// 监听窗口的变化
window.addEventListener("resize", this.listenerResize, false);
}
componentWillUnmount () {
// 执行解绑this.props.history.listen
unListen && unListen();
// 解绑窗口监听
window.removeEventListener("resize", this.listenerResize);
}
render() {
return (
<div>
</div>
);
}
}
export default Tabs;
完整代码
LeftMenu.jsx
javascript
import React, { Component } from "react";
import { Menu } from "antd";
import PubSub from "pubsub-js"; //引入
import { AppstoreOutlined, MailOutlined } from "@ant-design/icons";
import menuList from "../../config/menusConfig";
import root from "../../config/root"
import "./leftmenu.scss";
const { SubMenu } = Menu;
export default class menu extends Component {
state = {
collapsed: false,
selectedKeys: [],
openKeys: [],
menubar: [],
role: "companyAdmin"
};
componentDidMount () {
let userInfo = localStorage.getItem("userInfo");
if (userInfo) {
let { role } = JSON.parse(userInfo);
this.setState({ role })
}
// 点击Tabs组件时触发,进行菜单匹配
PubSub.subscribe("switch", (_, path) => {
this.setState({ selectedKeys: [path] });
this.judgeSubmenu(path);
}); //订阅
const menubar = this.creatMenu(menuList);
this.setState({
menubar,
});
}
toggleCollapsed = () => {
this.setState({
collapsed: !this.state.collapsed,
});
};
goPage = (path) => {
return () => {
this.props.goPage(path);
};
};
// 选择菜单
selectMenu = (obj) => {
this.setState({ selectedKeys: [obj.key] });
PubSub.publish("updateList"); //发布消息
};
openSubmenu = (obj) => {
this.setState({ openKeys: obj });
};
// 判断当前url对应的菜单,点击Tabs时需要进行匹配
judgeSubmenu (key) {
menuList.forEach((item) => {
if (item.children) {
item.children.forEach((itemChild) => {
if (itemChild.key === key) {
this.setState({ openKeys: [item.key] });
return item;
}
});
} else {
if (item.key === key) {
this.setState({ openKeys: [item.key] });
return item;
}
}
});
}
// 生成menu
creatMenu = (itemList) => {
return itemList.map((item) => {
if (this.judgement(item)) {
if (item.children) {
return (
<SubMenu
key={item.key}
title={item.title}
icon={<AppstoreOutlined />}
>
{this.creatMenu(item.children)}
</SubMenu>
);
} else {
return (
<Menu.Item
key={item.key}
icon={<MailOutlined />}
onClick={this.goPage(item.key)}
>
{item.title}
</Menu.Item>
);
}
}
});
};
// 判断当前登录的角色是否有权限查看该菜单项
judgement = (menu) => {
let { role } = this.state
if (root[role][menu.key].show) {
return true;
} else {
return false;
}
};
render () {
let { selectedKeys, openKeys, menubar } = this.state;
return (
<div style={{ width: 200 }} className="menu_box">
{/* <Button
type="primary"
onClick={this.toggleCollapsed}
style={{ marginBottom: 16 }}
>
{React.createElement(
this.state.collapsed ? MenuUnfoldOutlined : MenuFoldOutlined
)}
</Button> */}
<Menu
defaultSelectedKeys={["0"]}
defaultOpenKeys={["sub0"]}
mode="inline"
theme="dark"
selectedKeys={selectedKeys}
onClick={this.selectMenu}
onOpenChange={this.openSubmenu}
openKeys={openKeys}
>
{menubar}
</Menu>
</div>
);
}
}
Tabs.jsx
javascript
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
import {
CloseOutlined,
HomeOutlined,
RightSquareOutlined,
} from "@ant-design/icons";
import PubSub from "pubsub-js"; //引入
import "./tabs.scss";
let unListen; // this.props.history.listen 事件的返回值,用于解绑listen事件
class Tabs extends Component {
historyObj = {
"/admin/company": "公司管理",
"/admin/personnel": "人员管理",
"/admin/warehouse": "仓库管理",
"/admin/warehouse/detail": "仓库详情",
"/admin/maintenance": "维修厂管理",
"/admin/maintenance/detail": "维修厂详情",
"/admin/mac": "mac地址管理",
"/admin/steel": "型钢管理",
"/admin/project": "项目管理",
"/admin/project/detail": "项目详情",
"/admin/demand": "需求单管理",
"/admin/demand/detail": "需求单详情",
"/admin/clock/size": "规格尺寸",
"/admin/clock/material": "材料厂商",
"/admin/clock/manufacturing": "制造厂商",
"/admin/clock/transport": "运输公司",
"/admin/clock/price": "型钢单价",
"/admin/log": "日志管理",
"/admin/modify": "修改密码",
};
state = {
pathList: [], // 历史记录数组
activePath: "", // 当前的path
showFlag: true, // 展开与隐藏的标识
outHeight: 40, // tab外部盒子的高度,当tab的数量超过一行时会隐藏
tabHeight: 0, // tab内部的盒子高度,
};
componentDidMount () {
PubSub.subscribe("updateList", (_) => {
// console.log("数据更新");
if (this.tabDiv && this.tabDiv.clientHeight) {
this.setState({ tabHeight: this.tabDiv.clientHeight });
setTimeout(() => {
if (
!this.state.showFlag &&
this.outDiv.clientHeight < this.tabDiv.clientHeight
) {
this.setState({ outHeight: this.tabDiv.clientHeight });
}
}, 400);
}
}); //订阅
let activePath = localStorage.getItem("activePath");
this.setState({ activePath });
// 监听路由变化
unListen = this.props.history.listen((route) => {
// console.log(route.pathname); // 这个route里面有当前路由的各个参数信息
// console.log(route.pathname == "/admin/index");
localStorage.setItem("activePath", route.pathname);
this.setState({ activePath: route.pathname });
if (
route.pathname === "/admin/index" ||
route.pathname === "/" ||
route.pathname === "/login" ||
route.pathname === "/admin/error/not" ||
route.pathname === "/admin/error/redirect" ||
route.pathname === "/admin/error/systemerror"
) {
return;
}
// console.log(this.historyObj[route.pathname]);
let { pathList } = this.state;
let index = pathList.findIndex((item) => {
return item === route.pathname;
});
if (index === -1) {
pathList.push(route.pathname);
this.setState({ pathList });
}
});
window.addEventListener("resize", this.listenerResize, false);
}
// 浏览器窗口变化执行的函数
listenerResize = () => {
this.setState({});
if (this.outDiv.clientHeight > this.tabDiv.clientHeight) {
this.setState({ outHeight: this.tabDiv.clientHeight });
if (this.tabDiv.clientHeight === 40) {
let { showFlag } = this.state;
this.setState({ showFlag: !showFlag });
}
}
if (
!this.state.showFlag &&
this.outDiv.clientHeight < this.tabDiv.clientHeight
) {
this.setState({ outHeight: this.tabDiv.clientHeight });
}
this.setState({ tabHeight: this.tabDiv.clientHeight });
};
// 删除历史记录
removeHistory = (path) => {
return () => {
let { pathList } = this.state;
let list = pathList.filter((item) => {
return item !== path;
});
setTimeout(() => {
this.setState({ pathList: list });
if (this.outDiv.clientHeight > this.tabDiv.clientHeight) {
this.setState({
outHeight: this.tabDiv.clientHeight,
tabHeight: this.tabDiv.clientHeight,
});
if (this.tabDiv.clientHeight === 40) {
let { showFlag } = this.state;
this.setState({ showFlag: !showFlag });
}
}
}, 100);
};
};
// 页面跳转
goPage = (path) => {
return () => {
this.props.history.push({ pathname: path });
PubSub.publish("switch", path); //发布消息
};
};
goHome = () => {
// console.log("执行返回");
this.props.history.push({ pathname: "/admin/index" });
};
// 展示与隐藏
showFunc = () => {
let { showFlag } = this.state;
this.setState({ showFlag: !showFlag });
if (showFlag) {
this.setState({ outHeight: this.tabDiv.clientHeight });
} else {
this.setState({ outHeight: 40 });
}
};
componentWillUnmount () {
unListen && unListen(); // 执行解绑this.props.history.listen
window.removeEventListener("resize", this.listenerResize);
}
render () {
let { pathList, activePath, outHeight, tabHeight, showFlag } = this.state;
return (
<div
className="tab_out_box"
ref={(outDiv) => (this.outDiv = outDiv)}
style={{ height: outHeight }}
>
<div className="tabs_box" ref={(tabDiv) => (this.tabDiv = tabDiv)}>
<div
onClick={this.goHome}
className={
activePath === "" || activePath === "/admin/index"
? `home_box home_null_box`
: `home_box home_block_box`
}
>
<HomeOutlined style={{ fontSize: 18 }} />
</div>
{pathList.map((item) => {
return (
<div
className={item === activePath ? `tab_active tabs` : "tabs"}
key={item}
>
<div
className={
item === activePath ? `tabs_text_active tabs_text` : "tabs"
}
onClick={this.goPage(item)}
>
{this.historyObj[item]}
</div>
<CloseOutlined
onClick={this.removeHistory(item)}
style={{ fontSize: 12 }}
className={item === activePath ? `tabs_icon_active` : ""}
/>
</div>
);
})}
</div>
<div
className={tabHeight > 40 ? `spread_icon show_spread` : `spread_icon`}
>
<RightSquareOutlined
style={{ fontSize: 20 }}
onClick={this.showFunc}
className={
showFlag
? `square_outlined show_right`
: `square_outlined show_Down`
}
/>
{/* <DownSquareOutlined
style={{ fontSize: 20 }}
className= {showFlag ? `square_outlined show_Down` :`square_outlined`}
onClick={() => {
this.showFunc(false);
}}
/> */}
</div>
</div>
);
}
}
export default withRouter(Tabs);