Antd Checkbox CheckboxGroup Tab 实现多组多选联动

《加班搞》 第一篇 Antd Checkbox CheckboxGroup Tab多组多选联动

功能介绍:选择角色权限 ,有多个Tab,每个Tab里都有三级多选 ,我们抽象的可以理解共有三级,第一级全选当前tab所有Checkbox ,二级全选右侧所有Checkbox,三级多选联动。

如图所示:
功能图

数据格式:
数据格式
  1. Initdata 初始数据
  2. activeKey tab当前页的key
  3. convert_values() 处理初始数据(可忽略只针对我这个环境做的处理)
  4. convert_data()处理保存时的数据格式(可忽略只针对我这个环境做的处理)
  5. tab_convert()处理 tab数据格式(可忽略只针对我这个环境做的处理)

checkedList 的数据格式

checkedList 的数据格式

1.Tab PermissionTabs

const PermissionTabs = ({ permission, defaultCheckedKeys, inputOnChange }) => {
  const [activeKey, setActiveKey] = React.useState('node')      //选择tab的Key
  const [checkedList, setCheckedList] = React.useState(convert_values(defaultCheckedKeys)) // 所有选中数据
  const Initdata=convert_values(defaultCheckedKeys)      // 存储初始数据

  const tab_convert = (keys, sign) => { // 处理数据 可忽略
    if (!keys) return []
    let tabs = []
    keys.map(key => {
      permission.map(tab => {
        if (sign) {
          // if (tab.key.toLowerCase() != key.toLowerCase()) tabs.push(tab)
        } else {
          if (tab.key.toLowerCase() == key.toLowerCase()) tabs.push(tab)
        }
      })
    })
    return tabs
  }

  const convert_data = (checkedList) => { // 转换保存时的数据格式
    if (JSON.stringify(checkedList) == '{}') return []
    const data = Object.keys(checkedList).map(item => checkedList[item].concat(item))
    return _.flattenDeep(data)
  }

  const convert_values = (defaultCheckedKeys = []) => {  //处理初始数据
   const arr = defaultCheckedKeys.reduce((prev, cur, index, arr) => {
     if (cur.indexOf('.') != -1 && cur.indexOf('-') == -1) {
       const key_code = cur.split('.')
       prev[key_code[0]] = arr.filter(key => key.indexOf(key_code[0] + '.') != -1)
     }
     return prev
   }, {})
   return arr
 }

  React.useEffect(() => { // 存储数据
    inputOnChange(convert_data(checkedList))
  }, [checkedList])

 
  const callback = (key) => setActiveKey(key)

  return (
    <Tabs activeKey={activeKey} onChange={callback}>
      {tabs.map(tab => (
        <TabPane tab={tab.name} key={tab.key}>
          <CheckboxRow tab={tab}
            activeKey={activeKey}
            checkedList={checkedList}
            setCheckedList={setCheckedList}
            Initdata={Initdata}
          />
        </TabPane>
      ))}
    </Tabs>
  )
}

1.主要由Antd Checkbox的API ,checkedValue、checked,indeterminate几个状态控制CheckboxGroup与单个的checkbox的全勾选、半勾选、无勾选几种情况的联动。Checkbox多选框

2.checkAll 当前Tab的二级状态,也就是指每一行的左侧多选
3.activeKeyCheAll 当前Tab的一级全选状态
4.checkedList 所有选中的数据
5.indeterminate 当前tab里二级半选中状态
6.allInte 当前tab里一级半选中状态

2. 多选组 CheckboxRow 主要逻辑

const CheckboxRow = ({ tab, checkedList, setCheckedList, activeKey, Initdata }) => {

  const [ indeterminate, setIndeterminate ] = React.useState([])      //当前tab二级控非选中状态样式
  const [ allInte, setAllInte ] = React.useState({})                  //当前tab一级级非选中状态样式
  const [ checkAll, setCheckAll ] = React.useState({})                //当前tab二级选中状态
  const [ activeKeyCheAll, setActiveKeyCheAll ] = React.useState({})  //当前tab一级状态

  // 当前tab面板数据
  const tabpanel = tabs.filter((item) => item.key == activeKey)[0] || []
  const checks = tabpanel && tabpanel.child
  // 单选
  const onChange = (cdAllKey, checkedkeys, parentKey) => {
    let checkKey = {};
    let checked_list = {}
    checked_list[parentKey] = checkedkeys

    Object.keys(cdAllKey).forEach((key) => {
      checkKey[key] = 0;
      for (let checkedItem of checkedkeys || []) {
        if (cdAllKey[key].indexOf(checkedItem) != -1) {
          checkKey[key]++;
          checkAll[key] = checkKey[key] === cdAllKey[key].length;
          indeterminate[key] = !!checkKey[key] && (checkKey[key] < cdAllKey[key].length);
        }
      }
      if (checkKey[key] === 0) { // 选项组下仅有一个选项时取消选中
        checkAll[key] = false;
        indeterminate[key] = false
      }
    })

    setCheckedList({ ...checkedList, ...checked_list })
    setIndeterminate(indeterminate)
    setCheckAll(checkAll)
  }

  // 初始化
  React.useEffect(() => {
    if (JSON.stringify(Initdata) != '{}') {
      let Indet_erminate = {}, check_All = {}, currPanel = {}
      // 以下为初始值与当前tab页数据对比处理
      const Initdatas = Object.keys(Initdata).reduce((prev, cur) => {
        prev[cur] = Initdata[cur]
        return prev
      }, {})
      checks.forEach(item => { currPanel[item.key] = item.children.map(k => k.key) })
      // 初始值验证数组
      Object.keys(Initdatas).forEach(key => {
        const currlength = currPanel[key] && currPanel[key].length
        const Initlength = Initdatas[key] && Initdatas[key].length
        if (currlength <= Initlength) {
          check_All[key] = true
        } else if (Initlength < currlength) {
          Indet_erminate[key] = true
        }
      })
      // 当前tab页二级选项选中 vs 当前原始数据 控制一级全选
      const check_AllKeys = Object.keys(check_All)
      const currPanelKeys = Object.keys(currPanel)
      const active_KeyCheAll = {}, All_Inte = {}

      if (check_AllKeys.length == currPanelKeys.length) {
        active_KeyCheAll[activeKey] = true
      } else if (check_AllKeys.length < currPanelKeys.length) {
        All_Inte[activeKey] = true
      }
      setIndeterminate(Indet_erminate)
      setCheckAll(check_All)
      setAllInte(All_Inte)
      setActiveKeyCheAll(active_KeyCheAll)
    }
  }, [])

  // 监测当前tab数据变化改变一级 二级状态
  React.useEffect(() => {
    const All = Object.keys(checkAll).filter(key => checkAll[key]) || []
    const Checks = checks.map(item => item.key) || []
    const indeterminateaArr = Object.keys(indeterminate).filter(key => indeterminate[key] === true) || []

    if (All.length === Checks.length) {
      allInte[activeKey] = false
      let activeKey_CheAll = {}
      activeKey_CheAll[activeKey] = true
      setActiveKeyCheAll({ ...activeKeyCheAll, ...activeKey_CheAll })
      setAllInte(allInte)
    } else if (((All.length > 0) && (All.length < Checks.length)) || indeterminateaArr.length) {
      allInte[activeKey] = true
      setAllInte(allInte)
      setActiveKeyCheAll(_.omit(activeKeyCheAll, activeKey))
    }
  }, [ checkedList ])

  //二级全选
  const onCheckAllChange = (e, plainOptions, parentKey) => {
    const checked = e.target.checked
    if (checked === true) {
      let checked_list = {}
      checked_list[parentKey] = plainOptions.map(item => item.value)
      checkAll[parentKey] = true
      setCheckedList({ ...checkedList, ...checked_list })
      setIndeterminate(_.omit(indeterminate, parentKey))
      setCheckAll(checkAll)
    } else {
      setCheckedList(_.omit(checkedList, parentKey))
      setCheckAll(_.omit(checkAll, parentKey))
    }
  }

  // 一级选项
  const onAllChange = (e) => {
    const checked = e.target.checked
    let checkData = {}
    if (checked === true) {
      checks.forEach(item => {
        checkData[item.key] = item && item.children && item.children.map(check => check.key)
        checkAll[item.key] = true
        indeterminate[item.key] = false
      })
      activeKeyCheAll[activeKey] = true
      setCheckedList({ ...checkedList, ...checkData })
      setIndeterminate(indeterminate)
      setCheckAll(checkAll)
      setActiveKeyCheAll(activeKeyCheAll)

    } else {
      checks.forEach(item => {
        checkedList = _.omit(checkedList, item.key)
        checkAll[item.key] = false
        indeterminate[item.key] = false
      })
      activeKeyCheAll[activeKey] = false
      setCheckedList({ ...checkedList })
      setIndeterminate(indeterminate)
      setCheckAll(checkAll)
      setActiveKeyCheAll(activeKeyCheAll)
    }
  }

  return tab.child && tab.child.length > 0 ?
    <div><Checkbox style={{ margin: '0px 0px 10px 10px' }} indeterminate={allInte[activeKey]}
      checked={activeKeyCheAll[activeKey]} onChange={onAllChange}>全选</Checkbox> {
      tab.child.map(cd => (
        <Row gutter={[ 3, 16 ]} >
          <Col span={6}>
            <div className="Tabs-Col" >
              <Checkbox key={cd.key} indeterminate={indeterminate[cd.key]} checked={checkAll[cd.key]}
                onChange={(e) => onCheckAllChange(e, toLable(cd && cd.children || []), cd.key)}>
                {cd.title}
              </Checkbox></div>
          </Col>
          <Col span={18} >
            <div className="Tabs-Col Tabs-Col2">
              {cd && cd.children && cd.children.length > 0 ? <CheckboxCol cd={cd} checkedList={checkedList} onChange={onChange} /> : null}
            </div>
          </Col>
        </Row>
      ))}</div> : <Empty image={require('./../../../icon/homeIcon/noData.png')} imageStyle={{ height: 100 }} description="暂无数据"></Empty>
}

每次的OnChange都会把当前勾选的父级key(parentKey)、选中的key(checkedValue)、当前行的数据(cdAllKey)传给执行函数更加方便处理

3.CheckboxCol 每一行的右侧 Checkbox

const toLable = (data) => {
  if (data && data.length > 0) {
    return data.map(da => ({ label: da.title, value: da.key }))
  }
  return []
}

const CheckboxCol = (props) => {
  const { checkedList, onChange, cd: { key, children } } = props
  const options = toLable(children) // 转换数据格式 { label:'',value:'' }
  const cdAllKey = { [key]: options.map(item => item.value) } //点击行数据 key:[...]
  return <CheckboxGroup options={options} value={checkedList[key]} onChange={(checkedList) => onChange(cdAllKey, checkedList, key)} />
}

4.小结

这些就是本案例代码的核心部分,代码还有可优化空间 ,目前是最方便大家容易看懂的代码结构,后期优化好还会再次更新,希望对大家有所帮助。

5.写作感悟

《加班搞》作为我的第一个板块的第一期,和我第一次写作(处女作),还是想说点什么,哈哈。首先谢谢大家能看到这里 ,确实没有想到写作也是这么挺费神的,先要整理代码,梳理思路,怎么让大家清晰的看明白,这些都要考虑,不管怎么说也算完成了 ,有不足之处望大家多多包涵,多提宝贵意,在这里恳请各位在收藏的同时也点一点赞 (๑o๑) ,给我一点鼓励让我继续写下去~~谢谢大家捧场!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容