Commit d1f80bf8 authored by 张烨's avatar 张烨

feat: 树形选择器组件

parent 808a9293
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { debounce } from 'lodash';
import { Checkbox } from 'antd'; import { Checkbox } from 'antd';
import classnames from 'classnames';
import useMergeValue from 'use-merge-value';
import styles from './itemCard.less'; import styles from './itemCard.less';
const CheckGroup = Checkbox.Group; const CheckGroup = Checkbox.Group;
const getId = item => item.userID || item.roleID || item.stationID || item.id;
const isGroup = node => ['widgetGroup'].includes(node?.type || node);
const checkChildrenByCondition = (item, fn, tag = 'every') => {
if (!isGroup(item)) {
return fn(item);
}
return item.children[tag](t => checkChildrenByCondition(t, fn, tag));
};
const ListCardItem = props => { const ListCardItem = props => {
const { const {
getValueCallback, getValueCallback,
itemid, itemid,
// userList,
Child,
OUName,
searchWord, searchWord,
allChecked,
groupIndeterminate,
children, children,
text, text,
type,
isChecked,
...rest
} = props; } = props;
// id
const id = getId(rest);
// 当前checkbox为分组时有用,是否部分选中
const [indeterminate, setIndeterminate] = useState(false); const [indeterminate, setIndeterminate] = useState(false);
// 用来收集子节点的值
const [childValues, setChildValues] = useState({}); const [childValues, setChildValues] = useState({});
const [allChecked, setAllChecked] = useState(false); // 全选状态
const [checkList, setCheckList] = useState([]); // 复选框列表选中的值 // 是否是地图组件
const [defaultList, setDefaultList] = useState([]); // 默认的选项 const isMapWedgt = type === 'widgetUIPage';
const [submitArr, setSubmitArr] = useState([]); // 提交的数组
// const submitArr = []; // 当前组件是否是分组id
const isCurrentGroup = isGroup(type);
// 当前组件是否勾选
const [checked, setChecked] = useState(isChecked);
useEffect(() => { useEffect(() => {
let arr = []; // const all = children.every(u => checkAllChildrenChecked(u));
children.map((item, index) => { if (isCurrentGroup) {
let obj = { ...item }; // setIndeterminate(!all && children.some(u => checkSomeChildrenChecked(u)));
obj.label = ( } else {
<span // 初始进来报告自己的值
className={ getValueCallback(isChecked ? [id] : [], itemid);
searchWord && obj.text.includes(searchWord) ? styles.isSearch : '' setIndeterminate(false);
} }
> }, []);
{obj.text}
</span>
);
obj.value = obj.userID || obj.roleID || obj.stationID || obj.id;
arr.push(obj);
});
setDefaultList(arr);
}, [searchWord]);
// 监听父级是否勾选,如果父级勾选 则需要吧自己设置勾选状态并回传值
useEffect(() => { useEffect(() => {
let arr2 = []; if (allChecked !== checked && !groupIndeterminate) {
children.map((item, index) => { setChecked(allChecked);
if (item.isChecked) { if (!isCurrentGroup) {
arr2.push(item.userID || item.roleID || item.stationID || item.id); getValueCallback(allChecked ? [id] : [], itemid);
} }
});
// eslint-disable-next-line no-unused-expressions
getValueCallback && getValueCallback(arr2, itemid);
const all = children.every(u => u.isChecked);
setIndeterminate(!all && children.some(u => u.isChecked));
setAllChecked(all);
setCheckList(arr2);
}, [children]);
const handleAllChecked = e => {
const { checked } = e.target;
setAllChecked(checked);
let arr = [];
if (checked) {
arr = defaultList.map(item => item.value);
} }
setIndeterminate(false); }, [allChecked, groupIndeterminate]);
setCheckList(arr);
// eslint-disable-next-line no-unused-expressions
getValueCallback && getValueCallback(arr, itemid);
};
// 勾选事件处理
const handleChecked = e => { const handleChecked = e => {
setCheckList(e); const { checked: v } = e.target;
setAllChecked(e.length === defaultList.length); setChecked(v);
setIndeterminate(!!e.length && e.length < defaultList.length); setIndeterminate(false);
if (Child) { if (!isCurrentGroup) {
const childValArr = Object.values(childValues).flat(); getValueCallback(v ? [id] : [], itemid);
getValueCallback([...e, childValArr], itemid);
} else {
getValueCallback(e, itemid);
} }
}; };
const handleChildValueCallback = (arr, childIndex) => { const handleChildValueCallback = (arr, childIndex) => {
// 进这里的都是分组,组长统计并处理自己的状态: 全选,部分选,零选
// 拿到所有勾选的子节点
childValues[childIndex] = arr; childValues[childIndex] = arr;
const childValArr = Object.values(childValues).flat(); const childValArr = Object.values(childValues).flat();
// 保存组员状态
setChildValues({ ...childValues }); setChildValues({ ...childValues });
getValueCallback([...checkList, childValArr]);
// 递归执行的条件函数
const condition = c => childValArr.includes(getId(c));
const isAllchecked = checkChildrenByCondition(
{ children, type },
condition,
);
const isSomeChecked = checkChildrenByCondition(
{ children, type },
condition,
'some',
);
setIndeterminate(!isAllchecked && isSomeChecked);
setChecked(isAllchecked);
// eslint-disable-next-line no-unused-expressions
getValueCallback && getValueCallback([...childValArr, id], itemid);
}; };
const renderChild = () => const renderChild = () =>
Child && children &&
Child.map((c, i) => ( children.map((c, i) => (
<ListCardItem <ListCardItem
{...c} {...c}
itemid={i} itemid={i}
allChecked={checked}
groupIndeterminate={indeterminate}
key={`item${i}key`} key={`item${i}key`}
getValueCallback={handleChildValueCallback} getValueCallback={handleChildValueCallback}
searchWord={searchWord} searchWord={searchWord}
/> />
)); ));
if (defaultList.length === 0) {
return null;
}
return ( return (
<> <>
<div className={`${styles.divBox}`}> <div
className={classnames({
[styles.divBox]: isCurrentGroup,
[styles.divSingle]: !isCurrentGroup,
})}
>
<div className={styles.topCheckbox}> <div className={styles.topCheckbox}>
<Checkbox <Checkbox
indeterminate={indeterminate} indeterminate={indeterminate}
checked={allChecked} checked={checked}
onChange={e => { onChange={handleChecked}
handleAllChecked(e);
}}
> >
{text} {isMapWedgt ? (
<div
style={{ display: 'inline-block' }}
ref={r => {
if (r) r.innerHTML = text;
}}
/>
) : (
<span
className={classnames({
[styles.boldLabel]: isCurrentGroup,
})}
>
{text}
</span>
)}
</Checkbox> </Checkbox>
</div> </div>
<div style={{ width: '100%' }} className={styles.checkdiv}> <div style={{ width: '100%' }} className={styles.checkdiv}>
{defaultList && defaultList.length > 0 && (
<CheckGroup
className={styles.check}
onChange={e => {
handleChecked(e);
}}
value={checkList}
options={defaultList}
/>
)}
{renderChild()} {renderChild()}
</div> </div>
</div> </div>
......
...@@ -67,18 +67,35 @@ const ListCard = props => { ...@@ -67,18 +67,35 @@ const ListCard = props => {
}} }}
/> />
) : ( ) : (
dataList && <>
dataList.length > 0 && {dataList.some(d => d.type === 'widgetUIPage') && (
dataList.map((item, index) => ( <ListCardItem
<ListCardItem {...{
{...item} type: 'widgetGroup',
itemid={index} searchWord,
key={`item${index}key`} children: dataList.filter(d => d.type === 'widgetUIPage'),
getValueCallback={getValueCallback} text: '地图组件',
searchWord={searchWord} itemid: '9999',
{...props} }}
/> getValueCallback={getValueCallback}
)) searchWord={searchWord}
/>
)}
{dataList &&
dataList.length > 0 &&
dataList
.filter(d => d.type === 'widgetGroup')
.map((item, index) => (
<ListCardItem
{...item}
itemid={index}
key={`item${index}key`}
getValueCallback={getValueCallback}
searchWord={searchWord}
{...props}
/>
))}
</>
)} )}
{true && !loading && dataList && ( {true && !loading && dataList && (
<Button <Button
......
.divBox { .divBox {
display: flex; // display: flex;
width: 100%;
flex-wrap: wrap; flex-wrap: wrap;
border: 1px solid #b5b8c8; border: 1px solid #b5b8c8;
margin-top: 20px; margin-top: 20px;
min-height: 50px; min-height: 50px;
padding: 0 10px 10px 20px;
.ant-checkbox-wrapper{
background-color: #fff;
}
.topCheckbox{ .topCheckbox{
height: 20px; height: 20px;
background-color: #fff;
margin: -10px 0 0 20px; margin: -10px 0 0 20px;
line-height: 20px;
} }
.checkdiv { .checkdiv {
display: flex; display: flex;
margin-left: 20px; flex-wrap: wrap;
justify-content: space-between; // margin-left: 20px;
// justify-content: space-between;
} }
} }
.divSingle{
border: none;
margin-top: 20px;
flex:0 0 150px;
background: transparent;
}
.isSearch{ .isSearch{
color: red; color: red;
background-color: yellow; background-color: yellow;
} }
\ No newline at end of file .boldLabel{
font-size: 14px;
font-weight: bold;
background-color: #fff;
}
...@@ -4,7 +4,6 @@ import { Breadcrumb } from 'antd'; ...@@ -4,7 +4,6 @@ import { Breadcrumb } from 'antd';
import check from '@/components/Authorized/CheckPermissions'; import check from '@/components/Authorized/CheckPermissions';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import HeaderSearch from '../HeaderSearch'; import HeaderSearch from '../HeaderSearch';
import NoticeIcon from '../NoticeIcon';
import Avatar from './AvatarDropdown'; import Avatar from './AvatarDropdown';
import styles from './index.less'; import styles from './index.less';
...@@ -15,6 +14,7 @@ const routesOptions = route => { ...@@ -15,6 +14,7 @@ const routesOptions = route => {
}); });
return route.routes return route.routes
.map(r => { .map(r => {
if (r.hideMenu) return null;
if (r.authority) { if (r.authority) {
if (r.routes) { if (r.routes) {
return routesOptions(r); return routesOptions(r);
......
...@@ -51,6 +51,7 @@ const BasicLayout = props => { ...@@ -51,6 +51,7 @@ const BasicLayout = props => {
// footerRender={() => defaultFooterDom} // footerRender={() => defaultFooterDom}
rightContentRender={() => <RightContent />} rightContentRender={() => <RightContent />}
title="熊猫运维平台" title="熊猫运维平台"
fixedHeader
route={ route={
{ {
// routes: config.routes, // routes: config.routes,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment