lulujianglab / blog

:bento:lulujiang blog
https://lulujianglab.com/
83 stars 4 forks source link

定制化React三级菜单组件的创建与封装 #33

Closed lulujianglab closed 5 years ago

lulujianglab commented 5 years ago

由于项目的需要,最近开发了一些定制化的react组件,这篇文章主要介绍下呼出三级菜单的实现原理,支持鼠标悬浮呼出和鼠标点击呼出

image

事件机制

在事件机制中,主要利用鼠标的一些事件来监听,具体如下:

利用onClick(鼠标点击),onMouseOver(鼠标进入),onMouseLeave(鼠标离开)来监听鼠标的变化,同时在state状态机中定义控制菜单出现或消失的状态标识,然后通过这些鼠标事件来改变相应的状态值

class Hovermenu extends PureComponent {
  constructor(props) {
    super(props)
    this.state = {
      show: true,
      currentIndex: null,
      currentApplicationIndex: null,
      currentTabIndex: null,
    }
    this.handleMouseOver = this.handleMouseOver.bind(this)
    this.handleTabMouseOut = this.handleTabMouseOut.bind(this)
    this.setcurrentIndex = this.setcurrentIndex.bind(this)
    this.setoverSvg = this.setoverSvg.bind(this)
    this.setleaveSvg = this.setleaveSvg.bind(this)
    this.handleClick = this.handleClick.bind(this)
    this.handleDataMenu = this.handleDataMenu.bind(this)
  }

  componentDidMount () {
  }

  handleMouseOver(e) { 
    this.setState({
      currentApplicationIndex: parseInt(e.currentTarget.getAttribute('index'), 10),
      currentTabIndex: parseInt(e.currentTarget.getAttribute('index'), 10)
    })
  }

  handleTabMouseOut() {
    this.setState({
      currentTabIndex : null,
      currentApplicationIndex: null
    })
  }

  setcurrentIndex(e) {
    this.setState({
      currentIndex : parseInt(e.currentTarget.getAttribute('index'), 10)
    })
  }

  setoverSvg() {
    this.setState({
      show : false
    })
  }

  setleaveSvg() {
    this.setState({
      show : true
    })
  }

  handleClick() {
    this.setState({
      currentIndex : null
    })
  }

  // 处理菜单数据 将第三级菜单抽象成JSX
  handleDataMenu(arr) {
    const { currentIndex } = this.state
    return (
      <ul className={styles.card}>
        {
          arr.map((item,index) => {
            return (
              <a href='#' target="_blank" className={styles.text} key={item.name}>
                <li key={item.name} className={`${styles.item} ${currentIndex === index? styles.active : ''}`} index={index} onClick={this.setcurrentIndex} onMouseOver={this.setoverSvg} onMouseLeave={this.setleaveSvg} onFocus={() => 0}>
                  {item.name}
                </li>
              </a>
            )
          })
        }
      </ul>
    )
  }

  render () {
    const { currentTabIndex, currentApplicationIndex } = this.state
    const menuData = this.props.menu
    const title = this.props.title ? this.props.title  : '下拉菜单'
    const { trigger }= this.props
    // 一级菜单name
    const applicationArr = menuData.map(item => {
      return item.name
    })

    // 二级三级菜单数组
    const tabArr = menuData.map(item => {
      return item.children
    })

    // 一级菜单JSX
    const applicationList = (
      applicationArr.map((item,index) => {
        if ( trigger === 'click' ) {
          return (
            <div key={item} className={`${styles.item} ${currentApplicationIndex === index? styles.active : ''}`} index={index} onClick={this.handleMouseOver} onFocus={() => 0}>
              <div className={styles.title}>{item}</div>
            </div>
          )
        } else {
          return (
            <div key={item} className={`${styles.item} ${currentApplicationIndex === index? styles.active : ''}`} index={index} onMouseOver={this.handleMouseOver} onFocus={() => 0}>
              <div className={styles.title}>{item}</div>
            </div>
          )
        }
      })
    )

    // 二三级菜单分层处理
    const tabList = (
      tabArr.map((item,index) => {
        return (
          <div key={item[0].id} className={`${styles.item} ${currentTabIndex === index? styles.layer : styles.hidden}`} index={index} onMouseOver={this.handleMouseOver} onMouseOut={this.handleTabMouseOut} onClick={this.handleClick} onFocus={() => 0} onBlur={() => 0}>
            <Tabs defaultActiveKey="1" onChange={this.callback}>
              {
                item.map((element) => {
                  return <TabPane key={element.id} tab={element.name}>{this.handleDataMenu(element.children)}</TabPane>
                })
              }
            </Tabs>
          </div>
        )
      })
    )

    return (
      <Card title={title} bordered={false} className={styles.main}>
        <div className={styles.content}>
        {applicationList}
        </div>
        {tabList}
      </Card>
    )
  }
}

export default Hovermenu

样式设置

除了事件机制控制状态变化外,我们还需要在样式中设置父类和子类的position值,父类值为relative,子类值为absolute,同时为使悬浮菜单在最前端显示,菜单的css中需要加入层级控制z-index(数值越大,越靠前端)

同时需要注意的是,在hover判断时,需要在其中通过控制display来控制显示与否

如下面的.hidden { display: none }

.main {
  position: relative;
  .content {
    ...
  }
  .layer {
    position: absolute;
    z-index: 1050;
    left: 0;
    top: 144px;
    width: 100%;
    background-color: #f0f2f5;
    box-shadow: 0 2px 4px 0 #D9D9D9;
    border-radius: 2px;
    :global {
      .ant-tabs-nav .ant-tabs-tab {
        color: #666;
      }
    }
    .card {
      ...
    }
  }
  .hidden {
    display: none;
  }
}

封装

可见我的另一篇博客通过npm发布的第一个React组件的实践过程

Github Repo

前端React的三级菜单组件