关联展示
React 折腾记 - (3) 结合Mobx实现一个比较靠谱的动态tab水平菜单,同时关联侧边栏
React 折腾记 - (3) 结合Mobx实现一个比较靠谱的动态tab水平菜单,同时关联侧边栏
单个删除和删除其他的标签

只有一个时候是不允许关闭,所以也不会显示关闭的按钮,关闭其他也不会影响唯一的


React 折腾记 - (3) 结合Mobx实现一个比较靠谱的动态tab水平菜单,同时关联侧边栏
React 折腾记 - (3) 结合Mobx实现一个比较靠谱的动态tab水平菜单,同时关联侧边栏
多 tag 换行
React 折腾记 - (3) 结合Mobx实现一个比较靠谱的动态tab水平菜单,同时关联侧边栏
基础环境

mobx / react-router-dom / styled-components / react / antd

为了保持后台的风格一致化,直接基于 antd 的基础上封装一下

实现的思路基本是一样的(哪怕是自己把组件都写了)

实现思路 思路 用 mobx 来维护打开的菜单数据,数据用数组来维护 考虑追加,移除过程的去重 数据及行为的设计 结合路由进行响应 目标 点击 tab 展示页面内容,同时关联侧边栏的菜单 tab 自身可以关闭,注意规避只有一个的时候不显示关闭按钮,高亮的 杜绝重复点击 tab 的时候( tab 和路由匹配的情况),再次渲染组件 一键关闭除当前 url 意外的的所有 tab 可拓展的方向

有兴趣的自行拓展,具体 idea 如下

比如快速跳转到第一个或者最后一个的快捷菜单等 给侧边栏的子菜单都带上 icon ,这样把 icon 同步到水平菜单就比较好看了,目前水平都是直接写死 加上水波纹动效,目前没有..就是MD风格点一下扩散那种 拖拽,这样可以摆出更符合自己使用习惯的水平菜单 固定额外不被消除的标签,类似chrome的固定,不会给关闭所有干掉 代码实现

Model我们要考虑这么几点

侧边栏 item 的的组 key ,和子 key ,子 name 以及访问的 url 追加的 action ,删除的 action 只读的历史集合,只读的当前路由对象集合

思路有了.剩下就是东西的出炉了,先构建 model ,其实就是 mobx 数据结构

RouterStateModel.js import { observable, action, computed, toJS } from 'mobx'; function findObj(array, obj) { for (let i = 0, j = array.length; i < j; i++) { if (array[i].childKey === obj.childKey) { return true; } } return false; } class RouterStateModel { @observable currentUrl; // 当前访问的信息 @observable urlHistory; // 访问过的路由信息 constructor() { this.currentUrl = {}; this.urlHistory = []; } // 当前访问的信息 @action addRoute = values => { // 赋值 this.currentUrl = values; // 若是数组为0 if (this.urlHistory.length === 0) { // 则追加到数组中 this.urlHistory.push(this.currentUrl); } else { findObj(toJS(this.urlHistory), values) ? null : this.urlHistory.push(this.currentUrl); } }; // 设置index为高亮路由 @action setIndex = index => { this.currentUrl = toJS(this.urlHistory[index]); }; // 关闭单一路由 @action closeCurrentTag = index => { // 当历史集合长度大于一才重置,否则只剩下一个肯定保留额 this.urlHistory.splice(index, 1); this.currentUrl = toJS(this.urlHistory[this.urlHistory.length - 1]); }; // 关闭除了当前url的其他所有路由 @action closeOtherTag = route => { if (this.urlHistory.length > 1) { this.urlHistory = [this.currentUrl]; } else { return false; } }; // 获取当前激活的item,也就是访问的路由信息 @computed get activeRoute() { return toJS(this.currentUrl); } // 获取当前的访问历史集合 @computed get historyCollection() { return toJS(this.urlHistory); } } const RouterState = new RouterStateModel(); export default RouterState; 复制代码 Sidebar.js(侧边栏组件) import React, { Component } from 'react'; import { withRouter } from 'react-router-dom'; import { observer, inject } from 'mobx-react'; // antd import { Layout, Menu, Icon } from 'antd'; const { Sider } = Layout; const { SubMenu, Item } = Menu; import { sidebarData, groupKey } from 'pages/Layout/SidebarData'; // Logo组件 import Logo from 'pages/Layout/Logo'; import { rstat } from 'store/RouterStateModel'; @inject('rstat') @withRouter @observer class Sidebar extends Component { constructor(props) { super(props); // 初始化置空可以在遍历不到的时候应用默认值 this.state = { openKeys: [''], selectedKeys: [''], rootSubmenuKeys: groupKey, itemName: '' }; } setDefaultActiveItem = ({ location, rstat } = this.props) => { sidebarData.map(item => { if (item.pathname) { // 做一些事情,这里只有二级菜单 } // 因为菜单只有二级,简单的做个遍历就可以了 if (item.children && item.children.length > 0) { item.children.map(childitem => { // 为什么要用match是因为 url有可能带参数等,全等就不可以了 // 若是match不到会返回null if (location.pathname.match(childitem.path)) { this.setState({ openKeys: [item.key], selectedKeys: [childitem.key] }); // 设置title document.title = childitem.text; // 调用mobx方法,缓存初始化的路由访问 rstat.addRoute({ groupKey: item.key, childKey: childitem.key, childText: childitem.text, pathname: childitem.path }); } }); } }); }; componentDidMount = () => { // 设置菜单的默认值 this.setDefaultActiveItem(); }; componentDidUpdate = (prevProps, prevState) => { if (prevProps.location.pathname !== this.props.location.pathname) { this.setState({ openKeys: [this.props.rstat.activeRoute.groupKey], selectedKeys: [this.props.rstat.activeRoute.childKey] }); } }; OpenChange = openKeys => { const latestOpenKey = openKeys.find( key => this.state.openKeys.indexOf(key) === -1 ); if (this.state.rootSubmenuKeys.indexOf(latestOpenKey) === -1) { this.setState({ openKeys }); } else { this.setState({ openKeys: latestOpenKey ? [latestOpenKey] : [...openKeys] }); } }; // 路由跳转 gotoUrl = (itemurl, activeRoute) => { // 拿到路由相关的信息 const { history, location } = this.props; // 判断我们传入的静态路由表的路径是否和路由信息匹配 // 不匹配则允许跳转,反之打断函数 if (location.pathname === itemurl) { return; } else { // 调用mobx方法,缓存路由访问 this.props.rstat.addRoute({ pathname: itemurl, ...activeRoute }); history.push(itemurl); } }; render() { const { openKeys, selectedKeys } = this.state; const { collapsed, onCollapse } = this.props; const SideTree = sidebarData.map(item => ( <SubMenu key={item.key} title={ <span> <Icon type={item.title.icon} /> <span>{item.title.text}</span> </span> }> {item.children && item.children.map(menuItem => ( <Item key={menuItem.key} onClick={() => { // 设置高亮的item this.setState({ selectedKeys: [menuItem.key] }); // 设置文档标题 document.title = menuItem.text; this.gotoUrl(menuItem.path, { groupKey: item.key, childKey: menuItem.key, childText: menuItem.text }); }}> {menuItem.text} </Item> ))} </SubMenu> )); return ( <Sider collapsible breakpoint="lg" collapsed={collapsed} onCollapse={onCollapse} trigger={collapsed}> <Logo collapsed={collapsed} /> <Menu subMenuOpenDelay={0.3} theme="dark" openKeys={openKeys} selectedKeys={selectedKeys} mode="inline" onOpenChange={this.OpenChange}> {SideTree} </Menu> </Sider> ); } } export default Sidebar; 复制代码 DynamicTabMenu.js(动态菜单组件) import React, { Component } from 'react'; import styled from 'styled-components'; import { withRouter } from 'react-router-dom'; import { observer, inject } from 'mobx-react'; import { Button, Popover } from 'antd'; import { sidebarData } from 'pages/Layout/SidebarData'; import TagList from './TagList'; const DynamicTabMenuCSS = styled.div` box-shadow: 0px 1px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12); width: 100%; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; background-color: #fff; .tab-menu { flex: 1; } .operator { flex-shrink: 1; } `; @inject('rstat') @withRouter @observer class DynamicTabMenu extends Component { constructor(props) { super(props); this.state = { closeOtherTag: false // 控制关闭所有标签的状态 }; } initTagMenu = ({ location } = this.props) => { sidebarData.map(item => { // 因为菜单只有二级,简单的做个遍历就可以了 if (item.children && item.children.length > 0) { item.children.map(childitem => { // 为什么要用match是因为 url有可能带参数等,全等就不可以了 // 若是match不到会返回null if (location.pathname.match(childitem.path)) { this.setState({ currentTag: childitem.key }); } }); } }); }; componentDidMount = () => { // 设置tag高亮 this.initTagMenu(); }; // 关闭其他标签 closeOtherTagFunc = () => { this.props.rstat.closeOtherTag(); }; render() { const { rstate } = this.props; const { closeOtherTag } = this.state; return ( <DynamicTabMenuCSS> <div className="tab-menu"> <TagList /> </div> <div className="operator" onClick={this.closeOtherTagFunc} onMouseEnter={() => { this.setState({ closeOtherTag: true }); }} onMouseLeave={() => { this.setState({ closeOtherTag: false }); }}> <Popover placement="bottom" title="关闭标签" content={'只会保留当前访问的标签'} trigger="hover"> <Button type="dashed" shape="circle" icon="close" /> </Popover> </div> </DynamicTabMenuCSS> ); } } export default DynamicTabMenu; 复制代码 TagList.js(标签列表抽离) import React, { Component } from 'react'; import { withRouter } from 'react-router-dom'; import { observer, inject } from 'mobx-react'; import { Icon, Menu, Button } from 'antd'; @inject('rstat') @withRouter @observer class TagList extends Component { constructor(props) { super(props); this.state = { showCloseIcon: false, // 控制自身关闭icon currentIndex: '', // 当前的索引 selectedKeys: '' }; } render() { const { rstat, history, location } = this.props; const { showCloseIcon, currentIndex } = this.state; return ( <Menu selectedKeys={[rstat.activeRoute.childKey]} mode="horizontal"> {rstat.historyCollection && rstat.historyCollection.map((tag, index) => ( <Menu.Item key={tag.childKey} onMouseEnter={() => { this.setState({ showCloseIcon: true, currentIndex: tag.childKey }); }} onMouseLeave={() => { this.setState({ showCloseIcon: false }); }}> <span onClick={() => { rstat.setIndex(index); if (tag.pathname === location.pathname) { return; } else { history.push(tag.pathname); } }}> <Icon type="tag-o" style={{ padding: '0 0 0 10px' }} /> {tag.childText} </span> {showCloseIcon && rstat.historyCollection.length > 1 && currentIndex === tag.childKey ? ( <Icon type="close-circle" style={{ padding: '0 0 0 10px', position: 'absolute', top: 0, right: 0, marginRight: -12, zIndex: 999, fontSize: 24 }} onClick={() => { rstat.closeCurrentTag(index); history.push( rstat.activeRoute.pathname ); }} /> ) : null} </Menu.Item> ))} </Menu> ); } } export default TagList; 复制代码

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

tags: gt,lt,rstat,import,props,key,urlHistory,const,amp,pathname,openKeys
分页:12
转载请注明
本文标题:React 折腾记 - (3) 结合Mobx实现一个比较靠谱的动态tab水平菜单,同时关联侧边栏
本站链接:https://www.codesec.net/view/586942.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(53)