React实现配置化Tree树形控件(使用超简单)

React实现配置化Tree树形控件(使用超简单),第1张

React实现配置化Tree树形控件 一、效果图展示

二、使用方式 2.1导入图标

由于我这里是使用的阿里巴巴矢量图标库代替的选中框和展开、关闭的箭头,所以在使用的时候我们需要去阿里巴巴矢量图标库网址(点击这里)下载自己喜欢的样式框,我默认使用的是如下的图标(采用Font class方式引用)

2.2在组件中使用封装的Tree组件
export default function Example(){
  return (
    <Tree
      // 静态数据
      data={[
        {id:1,title:'笔记本电脑',value:'laptop',children:[
          {id:11,title:'华硕',value:'huaSuo',children:[
            {id:111,value:'a1',title:'a1'},
            {id:222,value:'a2',title:'a2'},
            {id:333,value:'a3',title:'a3'}
          ]},
          {id:22,value:'suoNi',title:'索尼'},
          {id:33,value:'lenovo',title:'联想'}
          ]
        },
        {id:2,value:'icebox',title:'冰箱'}
      ]}
      //配置数据(可选,下面的是默认的配置)
      config={{
        id:'id',
        title:'title',
        value:'value',
        children:'children',
        //默认是否展开(open),关闭(close)
        isOpen:'close',
        //配置箭头向下的图标样式Class的名字(使用阿里巴巴矢量图标库)
        openStyle:'icon-xiangxia',
        //配置箭头向右的图标样式Class的名字(使用阿里巴巴矢量图标库)
        closeStyle:'icon-xiangyoujiantou',
        //配置选中输入框的图标样式Class的名字(使用阿里巴巴矢量图标库)
        checkedStyle:'icon-duoxuankuang1',
        //配置未选中输入框的图标样式Class的名字(使用阿里巴巴矢量图标库)
        noCheckedStyle:'icon-duoxuankuang',
        //配置未全部选中输入框的图标样式Class的名字(半选盒子添加背景色即可)
        halfCheckedStyle:'half'
    }}
      // 获取选中的value
      getValue={(value)=>{
        console.log(value)
      }}
      //配置样式(可选)
      style={{color:'blue'}}
    >
    </Tree>
  )
}
三、具体实现 3.1设计思路

首先,需求是将选中的选中框存在一个数组里面,当我改变选中框时,这个数组的值也跟着改变,同时,如果这个选中框有子级或者父级的话,也相应的跟着变化,所以我们这里需要有两次遍历,一次是向上遍历,一次是向下遍历,最后,将设计好的需求封装成组件并且配置化,我们可以通过例子来进行使用我们封装好的组件。

3.2Tree组件js实现代码

在这里我使用阿里巴巴矢量图标来代替选中框(采用Font class的方式引用)

import React from 'react'
import './index.css'

//value值是否更新
let flag=false
function Tree(props) {
  //深拷贝
  const deepClone = (obj) => {
    let result = Array.isArray(obj) ? [] : {}
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (typeof obj[key] === 'object') {
          result[key] = deepClone(obj[key])
        } else {
          result[key] = obj[key]
        }
      }
    }
    return result
  }
  //返回value值
  let value=[]
  //默认关闭
  let isOpen='close'
  //初始化data数据值
  const init=(data,parent)=>{
    return data.map((item,index)=>{
      if(item[config.children]&&item[config.children].length>0){
        item.opened=isOpen
        // item.opened='open'
        item[config.children] = init(item[config.children],item)
      }else{
        item.opened=''
      }
      item.parent=parent
      item.checked=''
      return item
    })
  }
  //初始化配置文件
  const initConfig=(config)=>{
    let defaultConfig={
      id:'id',
      title:'title',
      value:'value',
      children:'children',
      openStyle:'icon-xiangxia',
      closeStyle:'icon-xiangyoujiantou',
      checkedStyle:'icon-duoxuankuang1',
      noCheckedStyle:'icon-duoxuankuang',
      halfCheckedStyle:'half'
    }
    if(!config){return defaultConfig}
    if(!config.id){config.id=defaultConfig.id}
    if(!config.title){config.title=defaultConfig.title}
    if(!config.value){config.value=defaultConfig.value}
    if(!config.children){config.children=defaultConfig.children}
    if(config.isOpen){config.isOpen==='open'?isOpen='open':isOpen='close'}
    if(!config.openStyle){config.openStyle=defaultConfig.openStyle}
    if(!config.closeStyle){config.closeStyle=defaultConfig.closeStyle}
    if(!config.checkedStyle){config.checkedStyle=defaultConfig.checkedStyle}
    if(!config.noCheckedStyle){config.noCheckedStyle=defaultConfig.noCheckedStyle}
    if(!config.halfCheckedStyle){config.halfCheckedStyle=defaultConfig.halfCheckedStyle}
    return config
  }
  const config=initConfig(props.config)
  const [data,setData]=React.useState([])
  React.useEffect(()=>{
    let data=[]
    props.data.forEach((item)=>{
      let obj=deepClone(item)
      data.push(obj)
    })
    setData(init(data))
  // eslint-disable-next-line react-hooks/exhaustive-deps
  },[])
  React.useEffect(()=>{
    if(flag){
      // eslint-disable-next-line react-hooks/exhaustive-deps
      value=[]
      getCheckedValue(data)
      props.getValue(value)
      // eslint-disable-next-line react-hooks/exhaustive-deps
      flag=false
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  },[data])
  //获取选中后的值
  const getCheckedValue=(data)=>{
    data.forEach((item,index)=>{
      getValue(item)
    })
  }
  const getValue=(obj)=>{
    if(obj.checked==='checked'){
      value.push(obj[config.value])
    }
    if(obj[config.children]){
      getCheckedValue(obj[config.children])
    }
  }
  //获取父级选中的状态值
  const getChecked=(item)=>{
    let hasUnChecked=item[config.children].map(item=>item.checked).indexOf('')!==-1
    let hasChecked=item[config.children].map(item=>item.checked).indexOf('checked')!==-1
    let hasHalf=item[config.children].map(item=>item.checked).indexOf('half')!==-1
    if(hasUnChecked&&!hasChecked&&!hasHalf){
      return ''
    }else if(!hasUnChecked&&hasChecked&&!hasHalf){
      return 'checked'
    }
    else{
      return 'half'
    }
  }
  //调整子级的状态值
  const adjust=(arr,checked)=>{
    arr.forEach(item => {
      item.checked=checked
      if(item[config.children]&&item[config.children].length>0){
        adjust(item[config.children],item.checked)
      }
    });
  }
  const adjustCheck=(item)=>{
    //调整父级
    let parent=item.parent
    while(parent){
      parent.checked=getChecked(parent)
      parent=parent.parent
    }
    //调整子级
    if(item[config.children]&&item[config.children].length>0){
      adjust(item[config.children],item.checked)
    }
  }
  const TreeOne=({data0,style,config})=>{
    return (
      <ul className='tree' style={style}>
        {
          data0.map((item,index)=>(
            <li className={`tree-item ${item.opened}`} key={item[config.id]}>
              <div className='tree-title'>
                <div className={`iconfont ${item.opened==='close'?config.closeStyle:item.opened===''?'':config.openStyle}`}
                  onClick={()=>{
                    if(item.opened!==''){
                      if(item.opened==='open'){
                        item.opened='close'
                      }else{
                        item.opened='open'
                      }
                      setData([...data])
                    }
                  }}
                ></div>
                <div className={`iconfont ${item.checked==='checked'?config.checkedStyle:item.checked===''?config.noCheckedStyle:config.halfCheckedStyle}`} 
                onClick={()=>{
                  if(item.checked===''){
                    item.checked='checked'
                  }else{
                    item.checked=''
                  }
                  adjustCheck(item)
                  flag=true
                  setData([...data])
                }}></div>
                <div className='item'>
                  {item[config.title]}
                </div>
              </div>
              {item[config.children]?<TreeOne data0={item[config.children]}  style={{paddingLeft:'15px'}} config={config}></TreeOne>:''}
            </li>
          ))
        }
      </ul>
    )
  }
  return (
    <TreeOne data0={data} config={config} style={props.style}/>
  )
}
3.3组件css样式
.tree {
  list-style: none;
  padding-left: 0px;
  margin: 0;
}

.tree .tree-item .tree-title {
  display: flex;
}
/* 是否展开箭头样式 */
.tree .tree-item .tree-title div:nth-child(1) {
  font-size: 15px;
  line-height: 30px;
  width: 15px;
  height: 15px;
}
/* 多选框样式 */
.tree .tree-item .tree-title div:nth-child(2) {
  font-size: 20px;
  color: #489ee2;
  line-height: 30px;
}
/* 内容样式 */
.tree .tree-item .tree-title div:nth-child(3) {
  line-height: 30px;
}

/* 半选样式 */
.tree .tree-item .tree-title .half {
  background-color: #489ee2;
  width: 15px;
  height: 15px;
  margin-top: 8px;
  margin-left: 5px;
  margin-right: 5px;
}

.tree .close > ul:nth-child(n) {
  display: none;
}

.tree .open > ul:nth-child(n) {
  display: block;
}
/* 阿里巴巴矢量图标库 */
@font-face {
  font-family: "iconfont";
  /* Project id 3310774 */
  src: url("iconfont.woff2?t=1649382069051") format("woff2"), url("iconfont.woff?t=1649382069051") format("woff"), url("iconfont.ttf?t=1649382069051") format("truetype");
}

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-duoxuankuang:before {
  content: "\e68c";
}

.icon-duoxuankuang1:before {
  content: "\e61c";
}

.icon-xiangxia:before {
  content: "\e600";
}

.icon-xiangyoujiantou:before {
  content: "\e65f";
}

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/944591.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-18
下一篇2022-05-18

发表评论

登录后才能评论

评论列表(0条)

    保存