谷歌Chrome浏览器风格的标签组件

选用技术
  • react
  • typescript
  • redux-saga存储本地标签数据
  • umi
实现
  • [x] 支持全部关闭,当前关闭,关闭其他Tab
  • [x] 支持Tab过多的自适应
  • [x] chrome风格
  • [x] 内圆角css
  1. 使用
import MenuTab from '@components/MenuTab'

<MenuTab location={props.location} />
  1. 代码
/**
* 菜单标签组件
* createDate: 2020年04月29日
*/
// menus数据结构:
// {
// title: '资料管理',
// path: '/DataManage',
// subs: [
// {
// title: '商品资料',
// path: '/Goods',
// fullPath: '/DataManage/Goods',
// },
// {
// title: '供应商查询',
// path: '/Suppliers',
// fullPath: '/DataManage/Suppliers',
// },
// ],
// },
import React, { useEffect, useRef, useState, useMemo } from 'react'
import { useHistory } from 'react-router-dom'
import { connect } from 'react-redux'
import classNames from 'classnames'
import { CloseOutlined, MoreOutlined } from '@ant-design/icons'
import menus from '@config/menus'
import { Popover } from 'antd'
import style from './Style.less' interface Iprop {
location: any
MenuTabStore: any
} interface ITab {
name: string
path: string
} /** 可优化点:redux匹配好存本地而不是每次打开页面都遍历,不过性能差距不大
* 根据path匹配name,返回
* @param menu 路由菜单
* @param path 当前路由
* @return ITab
*/
const matchRouterPath = (menu: any[], path: string, menuList: Array) => {
for (let item of menu) {
if (item.fullPath === path) {
menuList.push({
name: item.title,
path: item.fullPath
})
break;
}
// 为编辑页和详情页做补充
if (path.indexOf(item.fullPath) !== -1) {
if (path.indexOf('/Detail') !== -1) {
menuList.push({
name: item.title + '详情页',
path: path
})
}
if (path.indexOf('/Edit') !== -1) {
menuList[0] = {
name: item.title + '编辑/新增页', // 动态路由控制,为避免不和其他地方冲突,暂时写死
path: path
}
}
if (path.indexOf('/New') !== -1) {
menuList.push({
name: item.title + '新增页',
path: path
})
}
} if (item.fullPath !== path && item.subs && item.subs.length > 0) {
matchRouterPath(item.subs, path, menuList)
}
}
if (menuList.length) {
return menuList[0];
}
} const MenuTab = (props: Iprop) => {
const { location, dispatch = () => { }, MenuTabStore } = props
const { menuTabList } = MenuTabStore
const { pathname } = location
const dashboardPath = '/Home'
const history = useHistory()
const cTablimit = 0 // tab控制格式
const menuTabRef: any = useRef(null)
const [currentTabObj, setCurrentTabObj] = useState()
const isTabShow = pathname.match(/[/]/g).length > 1 // 暂定 二级路由才显示tabList // 是否当前tab
const isTabActive = (e: string) => {
return classNames(style.tab, {
[style.menuTabActive]: e === pathname
})
} const updateMenuTab = (tabList: any[]) => {
dispatch({
type: 'MenuTabStore/updateTabList',
payload: tabList
})
} useEffect(() => {
setCurrentTabObj(matchRouterPath(menus, pathname, []) || {})
}, [pathname]) // 路由变化才监听 useEffect(() => {
if (currentTabObj && Object.keys(currentTabObj).length) { // 不匹配的路由则无法更新到store
if (!menuTabList.length) { // tabs为空则直接concat
updateMenuTab(menuTabList.concat([currentTabObj]))
return
}
const isTabExist = menuTabList.filter(e => e.path === pathname)
if (isTabExist.length) { // tab已存在,不处理
return;
} else {
updateMenuTab(menuTabList.concat([currentTabObj]))
}
}
}, [currentTabObj]) // 当前tab存在才监听 // 切换tab
const toggleTab = (e: string) => {
history.replace(e)
} // tab控制是否显示
const isTabControlShow = () => {
if (menuTabList && menuTabList.length > cTablimit) {
return style.menuTabWrap + ' ' + style.showTabcontrol
}
return style.menuTabWrap
} // 关闭tab
const closeTabs: void = (e: any, type: string) => {
e.stopPropagation()
const clearTab = () => {
updateMenuTab([])
history.push(dashboardPath)
}
switch (type) {
case 'current':
if (menuTabList && menuTabList.length) {
const _i = menuTabList.findIndex((v) => v.path === pathname)
if (menuTabList && menuTabList.length > 1) {
const _arr = menuTabList.filter((e, i) => {
return i !== _i
})
updateMenuTab(_arr)
if (_arr.length === 1) { // 如果当前只剩一个tab
history.push(_arr[0].path)
} else {
history.goBack()
}
} else {
clearTab()
}
}
break;
case 'others':
updateMenuTab([
{
name: currentTabObj.name,
path: pathname
}
])
break;
case 'all':
clearTab()
break; default:
break;
}
} // 关闭指定tab
const btnCloseTab = (e: any, path: string) => {
e.stopPropagation()
if (currentTabObj.path === path) { // 当前
closeTabs(e, 'current')
return;
}
const _i = menuTabList.findIndex((v) => v.path === path)
const _arr = menuTabList.filter((e, i) => {
return i !== _i
})
updateMenuTab(_arr)
} const tabControlContent = (
<div className={style.tabControlPop}>
<div
onClick={(e) => {
closeTabs(e, 'current')
}}>
关闭当前标签页
</div>
<div
onClick={(e) => {
closeTabs(e, 'others')
}}>
关闭其他标签页
</div>
<div
onClick={(e) => {
closeTabs(e, 'all')
}}>
关闭全部标签页
</div>
</div>
) // 太多tab自适应
const [rect, setRect] = useState(menuTabRef.current?.getBoundingClientRect())
console.log('rect', rect);
useEffect(() => {
setRect(menuTabRef.current?.getBoundingClientRect())
window.addEventListener('resize', () => {
setRect(menuTabRef.current?.getBoundingClientRect())
})
return () => {
window.removeEventListener('resize', () => {
setRect(menuTabRef.current?.getBoundingClientRect())
})
}
}, [])
const rectWidth = useMemo(() => {
return rect ? rect.width - 18 : '100%'
}, [rect])
const tabWidth = useMemo(() => {
if (typeof rectWidth === 'string') {
return '100%'
}
return rectWidth / menuTabList.length - 3
}, [rectWidth, menuTabList.length])
return isTabShow && menuTabList.length ? (
<div ref={menuTabRef} className={style.menuContainer}>
<div style={{ width: rectWidth }} className={isTabControlShow()}>
{menuTabList.map((e) => {
const { path, name } = e
return (
<div
key={path}
className={`list-tab ${isTabActive(path)}`}
style={{ width: tabWidth }}
onClick={() => {
toggleTab(path)
}}
>
<div key={e} className='ellipsis'>
{name}
</div>
<div className={style.closeTabIcon}>
<CloseOutlined
className={style.tabClose}
onClick={(item) => {
btnCloseTab(item, path)
}}
/>
</div>
</div>
)
})}
</div>
<Popover content={tabControlContent} placement='bottomRight'>
<div className={style.moreOut}>
<MoreOutlined
className={menuTabList && menuTabList.length > cTablimit ? style.showMore : style.hideMore}
/>
</div>
</Popover>
</div>
) : null
} export default connect(({ MenuTabStore }: any) => ({
MenuTabStore
}))(MenuTab)

github

react ts redux-saga | 谷歌Chrome浏览器风格的标签组件 | 中台的更多相关文章

  1. 使用谷歌chrome浏览器查看任何标签的固有属性

    查看任何标签的固有属性property: 使用谷歌浏览器:Ctrl+Shift+I 开发者工具----点击Elements----点击a标签----点击Properties属性及其属性值. 自定义属性 ...

  2. Clover 3 --- Windows Explorer 资源管理器的一个扩展,为其增加类似谷歌 Chrome 浏览器的多标签页功能。

    http://cn.ejie.me/ http://cn.ejie.me/uploads/setup_clover@3.4.6.exe  软件下载 默认图标实在比较难看,更换图标 更改图标---选择图 ...

  3. 谷歌Chrome浏览器开发者工具的基础功能

    上一篇我们学习了谷歌Chrome浏览器开发者工具的基础功能,下面介绍的是Chrome开发工具中最有用的面板Sources.Sources面板几乎是最常用到的Chrome功能面板,也是解决一般问题的主要 ...

  4. 谷歌chrome浏览器和火狐firefox浏览器自带http抓包工具和请求模拟插件

    谷歌chrome浏览器自带http抓包工具 chrome://net-internals/ 谷歌chrome浏览器http请求模拟插件:postman 火狐http请求模拟插件:httprequest ...

  5. 在 Ubuntu 16.04 中安装谷歌 Chrome 浏览器

    进入 Ubuntu 16.04 桌面,按下 Ctrl + Alt + t 键盘组合键,启动终端. 也可以按下 Win 键(或叫 Super 键),在 Dash 的搜索框中输入 terminal 或&q ...

  6. Ubuntu小技巧——怎样安装谷歌Chrome浏览器

    对于刚刚开始使用Ubuntu并想安装谷歌Chrome浏览器的新用户来说,本文所介绍的方法是最快捷的.在Ubuntu上安装谷歌Chrome的方法有很多.一些用户喜欢直接在谷歌Chrome下载页面获得 d ...

  7. 关于如何解决谷歌Chrome浏览器空白页的问题

    谷歌Chrome浏览器突然不打开任何网页,无论是任何站点(如http://www.baidu.com), 还是Chrome浏览器的设置页面(chrome://settings/), 扩展页面 ( ch ...

  8. 谷歌Chrome浏览器提示adobe flash player已过期完美解决办法

    最近使用谷歌Chrome浏览器提示adobe flash player已过期,浏览网页时一些flash元素的东西都无法正常显示,在网上尝试寻找很多方法,都不能解决,最后,经测试有效方法如下:一:下载最 ...

  9. Ubuntu 16下安装64位谷歌Chrome浏览器

    Ubuntu 16下安装64位谷歌Chrome浏览器 1.将下载源加入到系统的源列表 在终端中,输入以下命令: sudo wget https://repo.fdzh.org/chrome/googl ...

随机推荐

  1. jira仪表盘的建立与共享

    一般在项目测试阶段,可以通过jira仪表盘清晰的展示bug的各个状态的数量,各个开发人员的bug数量. 有效督促开发解决问题. 也为测试日报提供了良好的数据支持,减少人工统计的工作量. 1.建议筛选器 ...

  2. 【Nginx】如何获取客户端真实IP、域名、协议、端口?看这一篇就够了!

    写在前面 Nginx最为最受欢迎的反向代理和负载均衡服务器,被广泛的应用于互联网项目中.这不仅仅是因为Nginx本身比较轻量,更多的是得益于Nginx的高性能特性,以及支持插件化开发,为此,很多开发者 ...

  3. Windows故障转移群集(WSFC)的备份和恢复

    使用wbadmin进行备份和恢复将C盘数据备份到E盘查看备份的版本以及包含的items模拟群集角色被误删除进行恢复操作检查恢复的效果 WSFC群集的备份和恢复功能是使用Windows Server B ...

  4. 008.Nginx静态资源

    一 Nginx静态资源概述 1.1 静态资源类型 Nginx作为静态资源Web服务器部署配置, 传输非常高效, 常常用于静态资源处理,请求以及动静分离.通常非服务器动态运行生成的文件属于静态资源. 类 ...

  5. 【SpringBoot】 中时间类型 序列化、反序列化、格式处理

    [SpringBoot] 中时间类型 序列化.反序列化.格式处理 Date yml全局配置 spring: jackson: time-zone: GMT+8 date-format: yyyy-MM ...

  6. css : 使用浮动实现左右各放一个元素时很容易犯的错误

    比如说,有一个div,我想在左侧和右侧各方一个元素. 如果不想用flex,那就只能用浮动了. ... <div class="up clearfix"> <h6& ...

  7. C# 判断和创建目录路径

    在进行一些导出或下载时,需要创建一个本地路径,以供文件进行下载和创建. if (Directory.Exists(Server.MapPath("~/upimg/hufu")) = ...

  8. CSS把容器中的内容限制行数,在超过行数后,在最后一行显示"..."

    <style type="text/css"> .main{ width: 400px; background-color: #3498db; display: -we ...

  9. SpringBoot + Spring Cloud Consul 服务注册和发现

    什么是Consul Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其它分布式服务注册与发现的方案,Consul 的方案更"一站式" ...

  10. 小书MybatisPlus第6篇-主键生成策略精讲

    本文为mybatis系列文档的第6篇,前5篇请访问下面的网址. 小书MybatisPlus第1篇-整合SpringBoot快速开始增删改查 小书MybatisPlus第2篇-条件构造器的应用及总结 小 ...