
由于我这里是使用的阿里巴巴矢量图标库代替的选中框和展开、关闭的箭头,所以在使用的时候我们需要去阿里巴巴矢量图标库网址(点击这里)下载自己喜欢的样式框,我默认使用的是如下的图标(采用Font class方式引用)
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";
}
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)