树形组件Tree附带选择check选择框

11 篇文章 0 订阅
8 篇文章 0 订阅

在这里插入图片描述
手搓了一个组件,放在博客里说不定自定义的时候用的到,用组件库根据项目需要想改加功能非常头疼,所以我喜欢自己写组件,代码全部放上去了,复制就能用哈,选中数据返回我没写,因为不知道用的时候需要那种格式,这个结果返回很简单的
3个文件:tree主文件,item树形子文件,check选择框,之所以拆分这个check,方便我后面写其他组件直接用组件打包的下载地址
文件结构示例
在这里插入图片描述
使用展示

import React from 'react';
import Tree from '@/compontent/form/tree';

const treeOption = [];
const ceshi = () => {
  function treeChange(param) {
    console.log(param);
  }

  return <Tree change={treeChange.bind(this)} options={treeOption} />;
};
export default ceshi;

tree - index.tsx

import React, { useState, useEffect } from 'react';
import Item from './item/index';
import './index.less';
import * as Interface from './interface';

// checked  0:未选中 1选中 2子元素部分选中
const UiTree = (props: Interface.Iprop) => {
  const { options, change } = props;
  const [list, setList] = useState<Array<Interface.ListItemT>>([]);

  // 子项展开/收起
  function visibleChange(item: Interface.ListItemT) {
    item.visible = !item.visible;
    setList([...list]);
  }
  // 选中切换
  function checkedChange(item: Interface.ListItemT, indexList) {
    item.checked = item.checked === 0 ? 1 : 0;
    if (item.children) {
      for (let i = 0; i < item.children.length; i++) {
        checkedChildren(item.children[i], item.checked);
      }
    }
    setList([...list]);
    if (indexList.length !== 1) {
      checkedFather(indexList);
    }
    change(list);
  }
  // 子项检测
  function checkedChildren(item: Interface.ListItemT, value) {
    item.checked = value;
    setList([...list]);
    if (item.children) {
      for (let i = 0; i < item.children.length; i++) {
        checkedChildren(item.children[i], item.checked);
      }
    }
  }
  // 父级检测-获取到子项的索引-反推父项并核对节点状态
  function checkedFather(indexList) {
    const arr = recursionFn(indexList, list, 0);
    let count = 0;
    const len = arr.children.length;
    for (let i = 0; i < len; i++) {
      if (arr.children[i].checked === 2) {
        count = -1;
        break;
      }
      if (arr.children[i].checked === 1) {
        count++;
      }
    }
    if (count === 0) {
      arr.checked = 0;
    } else if (count === len) {
      arr.checked = 1;
    } else {
      arr.checked = 2;
    }
    setList([...list]);
    if (indexList.length > 2) {
      indexList.splice(indexList.length - 1, 1);
      checkedFather(indexList);
    }
  }
  // 通过索引反推获取父级
  function recursionFn(indexList, arr, index: number) {
    if (indexList.length - 2 === index) {
      return arr[indexList[index]];
    }
    if (index < indexList.length - 2) {
      const num = index + 1; //不可使用index++代替
      return recursionFn(indexList, arr[indexList[index]].children, num);
    }
  }
  // 树节点渲染
  function renderItem(newlist: Array<Interface.ListItemT>, num = 0, fatherIndex: Array<number> = []) {
    if (!newlist) return;
    return newlist.map((item, index) => {
      if (!item.children || item.children.length === 0) {
        return <Item checkedChange={checkedChange.bind(this, item, [...fatherIndex, index])} key={index} params={{ ...item, left: num }} />;
      } else {
        return (
          <div style={{ marginLeft: `${num}px` }} className="ui-tree-children" key={index}>
            <div className="ui-tree-father">
              <span onClick={visibleChange.bind(this, item)} className={`ui-tree-icon ui-tree-${item.visible ? '' : 'iconright'}`}>
                <i className="iconfont icon-caret-down"></i>
              </span>
              <Item checkedChange={checkedChange.bind(this, item, [...fatherIndex, index])} key={index} params={{ ...item, left: 0 }} />
            </div>
            <div
              style={{
                height: item.visible ? '' : '0px'
              }}
              className="ui-tree-panel"
            >
              {renderItem(item.children, 20, [...fatherIndex, index])}
            </div>
          </div>
        );
      }
    });
  }
  // 数据过滤
  function listFilter(arr) {
    return arr.map(item => {
      if (item.children && item.children.length > 0) return { visible: true, checked: 0, ...item, children: listFilter(item.children) };
      else return { visible: true, checked: 0, ...item };
    });
  }
  useEffect(() => {
    setList(listFilter(options));
  }, [options]);

  return <div className="ui-tree">{renderItem(list)}</div>;
};
export default UiTree;

tree - index.less

.ui-tree {
  &-father {
    display: flex;
  }
  &-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    cursor: pointer;
    transition: all 0.3s;
  }
  &-iconright {
    transform: rotate(-90deg);
  }
  &-panel {
    overflow: hidden;
    transition: all 0.3s;
  }
}

tree - interface.ts

export type ListItemT = {
  label: string;
  value: number | string;
  visible?: boolean;
  checked?: number;
  children?: Array<ListItemT>;
};
export interface Iprop {
  options: Array<ListItemT>;
  change: (param: Array<ListItemT>) => void;
}

export const treeOption = [
  {
    label: '选项1',
    value: 0,
    visible: true,
    checked: 0,
    children: [
      {
        label: '选项1-1',
        value: 0,
        visible: true,
        checked: 0,
        children: [
          {
            label: '选项1-1-1',
            value: 0,
            visible: true,
            checked: 0,
            children: [
              {
                label: '选项1-1-1-1',
                value: 0,
                visible: true,
                checked: 0
              },
              {
                label: '选项1-1-1-2',
                value: 0,
                visible: true,
                checked: 0
              }
            ]
          }
        ]
      },
      {
        label: '选项1-2',
        value: 0,
        visible: true,
        checked: 0,
        children: [
          {
            label: '选项1-2-1',
            value: 0,
            visible: true,
            checked: 0,
            children: [
              {
                label: '选项1-2-1-1',
                value: 0,
                visible: true,
                checked: 0
              },
              {
                label: '选项1-2-1-2',
                value: 0,
                visible: true,
                checked: 0
              }
            ]
          }
        ]
      }
    ]
  },
  {
    label: '选项2',
    value: 0,
    visible: true,
    checked: 0,
    children: [
      {
        label: '选项2-1',
        value: 0,
        visible: true,
        checked: 0
      },
      {
        label: '选项2-2',
        value: 0,
        visible: true,
        checked: 0
      }
    ]
  },
  {
    label: '选项3',
    value: 0,
    visible: true,
    checked: 0
  }
];

tree - checkbox - index.tsx

import React, { useState } from 'react';
import './index.less';

interface Iprops {
  checked: number;
}
const statusStatic = ['', 'checked', 'indeterminate'];
const UiCheckbox = (props: Iprops) => {
  const { checked } = props;
  const [status] = useState(statusStatic);
  return (
    <div className="ui-checkbox">
      <span className={`ui-tree-checkbox ui-tree-checkbox-${status[checked]}`}>
        <span className="ui-tree-checkbox-inner"></span>
      </span>
    </div>
  );
};
export default UiCheckbox;

tree - checkbox - index.less

.ui-checkbox {
  display: inline-block;
}
.ui-tree-checkbox {
  box-sizing: border-box;
  top: 3px !important;
  margin: 0;
  padding: 0;
  color: #000000d9;
  font-size: 14px;
  font-variant: tabular-nums;
  list-style: none;
  font-feature-settings: 'tnum';
  position: relative;
  line-height: 1;
  white-space: nowrap;
  outline: none;
  cursor: pointer;
}
.ui-tree-checkbox-checked .ui-tree-checkbox-inner {
  background-color: #1890ff;
  border-color: #1890ff;
}
.ui-tree-checkbox-inner {
  position: relative;
  top: 0;
  left: 0;
  display: inline-block !important;
  width: 16px;
  height: 16px;
  direction: ltr;
  background-color: #fff;
  border: 1px solid #d9d9d9;
  border-radius: 2px;
  border-collapse: separate;
  transition: all 0.3s;
}
.ui-tree-checkbox-checked .ui-tree-checkbox-inner:after {
  position: absolute;
  display: table;
  border: 2px solid #fff;
  border-top: 0;
  border-left: 0;
  transform: rotate(45deg) scale(1) translate(-50%, -50%);
  opacity: 1;
  transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s;
  content: ' ';
}

.ui-tree-checkbox-inner:after {
  position: absolute;
  top: 50%;
  left: 21.5%;
  display: table;
  width: 5.71428571px;
  height: 9.14285714px;
  border: 2px solid #fff;
  border-top: 0;
  border-left: 0;
  transform: rotate(45deg) scale(0) translate(-50%, -50%);
  opacity: 0;
  transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s;
  content: ' ';
}
.ui-tree-checkbox-checked:after {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: 1px solid #1890ff;
  border-radius: 2px;
  visibility: hidden;
  animation: antCheckboxEffect 0.36s ease-in-out;
  animation-fill-mode: backwards;
  content: '';
}
.ui-tree-checkbox-indeterminate .ui-tree-checkbox-inner:after {
  top: 50%;
  left: 50%;
  width: 8px;
  height: 8px;
  background-color: #1890ff;
  border: 0;
  transform: translate(-50%, -50%) scale(1);
  opacity: 1;
  content: ' ';
}

tree - item - index.tsx

import React from 'react';
import * as Interface from '../interface';
import UiCheckBox from '../checkbox/index';
import './index.less';

interface Iprops {
  params;
  checkedChange: (param: Interface.ListItemT) => void;
}
const UiTree = (props: Iprops) => {
  const { params, checkedChange } = props;

  return (
    <div style={{ marginLeft: `${params.left}px` }} className="ui-tree-item">
      {params.children ? '' : <span className="ui-tree-space"></span>}
      <span onClick={checkedChange.bind(this, params)}>
        <UiCheckBox checked={params.checked} />
      </span>
      <div className="ui-tree-label">{params.label}</div>
    </div>
  );
};
export default UiTree;

tree - item - index.less

.ui-tree-item {
  display: flex;
}
.ui-tree {
  &-space {
    display: inline-block;
    width: 20px;
    height: 20px;
    background-color: #fff;
  }
  &-label {
    padding: 0 10px;
  }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值