zoukankan      html  css  js  c++  java
  • react ts redux-saga | 谷歌Chrome浏览器风格的标签组件 | 中台

    谷歌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={{  rectWidth }} className={isTabControlShow()}>
            {menuTabList.map((e) => {
              const { path, name } = e
              return (
                <div
                  key={path}
                  className={`list-tab ${isTabActive(path)}`}
                  style={{  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

  • 相关阅读:
    Android Tween和Frame 动画
    Android Tween和Frame 动画
    android:descendantFocusability用法简析
    android:descendantFocusability用法简析
    CodeForces 686A Free Ice Cream (水题模拟)
    UVa 11136 Hoax or what (STL)
    UVa 1616 Caravan Robbers (二分+贪心)
    UVa 10570 Meeting with Aliens (暴力)
    UVa 1153 Keep the Customer Satisfied (贪心+优先队列)
    HDU 2044 一只小蜜蜂...(递推,Fibonacci)
  • 原文地址:https://www.cnblogs.com/mapleChain/p/12803958.html
Copyright © 2011-2022 走看看