基于antd实现自定义多级层级组件,主要使用react+antd+typescript实现具体内容:
组件展示效果:
数据json格式:
[{
text: '浙江省',
value: '330000',
children: [{
text: '杭州市',
value: '330100',
children: [{
text: '上城区',
value: '330102',
}, {
text: '下城区',
value: '330103',
}, {
text: '江干区',
value: '330104',
}]
}, {
text: '宁波市',
value: '330200',
children: [{
text: '海曙区',
value: '330203',
}, {
text: '江北区',
value: '330205',
}, {
text: '北仑区',
value: '330206',
}]
}, {
text: '温州市',
value: '330300',
children: [{
text: '鹿城区',
value: '330302',
}, {
text: '龙湾区',
value: '330303',
}, {
text: '瓯海区',
value: '330304',
}]
}]
}, {
text: '江苏省',
value: '320000',
children: [{
text: '南京市',
value: '320100',
children: [{
text: '玄武区',
value: '320102',
}, {
text: '秦淮区',
value: '320104',
}, {
text: '建邺区',
value: '320105',
}]
}, {
text: '无锡市',
value: '320200',
children: [{
text: '锡山区',
value: '320205',
}, {
text: '惠山区',
value: '320206',
}, {
text: '滨湖区',
value: '320211',
}]
}, {
text: '徐州市',
value: '320300',
children: [{
text: '鼓楼区',
value: '320302',
}, {
text: '云龙区',
value: '320303',
}, {
text: '贾汪区',
value: '320305',
}]
}]
}]
CascaderList.tsx 组件源码:
import { useState, memo, useMemo, useEffect } from 'react';
import { Popover, Cascader } from 'antd';
import { CheckOutlined, CaretDownOutlined, CloseCircleFilled } from '@ant-design/icons';
import '@/assets/style/cascaderList.less';
type objTypes = {
[key: string]: any
}
type fileNameTypes = {
text: string,
value: string,
children: string
}
type propTypes = {
column?: number, //几级层级联动
fileNames?: fileNameTypes, //选项列表中的属性{text: 'text', value: 'value', children: 'children'}
options: Array<objTypes>, // 配置选项的列表
checkedValue?: Array<any>, //选中的数据
placeholder?: string, //输入占位符
separator?: string, //分隔符
onFinish?: Function, //全部选中的回调函数
render?: Function, //自定义渲染内容
children?: JSX.Element
}
// 将树形结构转换成数组
function treeToList(tree: Array<objTypes>) {
let queue = [...tree];
let res: Array<objTypes> = [];
queue.concat(tree);
while (queue.length != 0) {
let obj = queue.shift();// 弹出队首元素
if (obj?.children) {
queue = queue.concat(obj.children);// 子节点入队
// delete obj["children"];// 删除 children 属性
}
res.push(obj as objTypes);
}
return res;
};
// 配置显示的导航
function configNav<T>(checkedList: Array<T>, col: number): Array<T> {
let arr: Array<T> = [];
if (checkedList.length >= col) {
arr = checkedList;
} else {
arr = [...checkedList, '请选择' as T];
}
return arr;
};
// 将字段转换显示
function renderTextCallback<T>(checkedValue: Array<T>, allList: Array<objTypes>, fileNames: fileNameTypes, keyText: string): Array<T> {
if (!checkedValue.length) return [];
let key = keyText === 'text' ? fileNames.value : fileNames.text;
return checkedValue.map(item => {
let findItem = allList.find((el: objTypes) => el[key] === item);
return (findItem ? findItem[keyText] : '');
})
};
/* 渲染层级联动列表 */
function CascaderList(props: propTypes) {
const { column = 3, fileNames = {
text: 'text',
value: 'value',
children: 'children'
},
checkedValue = [], options = [], separator = '/', placeholder, onFinish, render } = props;
const allCascaderList = useMemo(() => {
return treeToList(options);
}, [])
// 控制弹框显示
const [open, setOpen] = useState(false);
// 当前选中数据
const [checkedList, setCheckedList] = useState<string[]>(() => renderTextCallback<string>(checkedValue, allCascaderList, fileNames, 'text'));
// 当前渲染选中
const [renderCheckedText, setRenderCheckedText] = useState(checkedList);
// 当前选中的导航
const [activeKey, setActiveKey] = useState(checkedValue.length ? checkedValue.length - 1 : 0);
// 配置导航
const [tabList, setTabList] = useState(() => configNav(checkedList, column));
useEffect(() => {
if (!checkedValue.length) {
setCheckedList([]);
setRenderCheckedText([]);
setActiveKey(0);
setTabList(configNav([], column));
}
}, [JSON.stringify(checkedValue)])
/* 修改导航变化 */
const onChange = (key: number) => {
if (key === activeKey) return;
setActiveKey(key);
let newTabList = tabList.filter(item => item !== '请选择');
if (JSON.stringify(newTabList) !== JSON.stringify(tabList)) {
setTabList(newTabList);
};
};
/* 点击选中层级 */
const handleSelectCascader = (e: any, label: string) => {
e.stopPropagation();
// 设置选中数据
let newCheckedList = checkedList.slice(0, activeKey);
newCheckedList.push(label);
setCheckedList(newCheckedList);
if (newCheckedList.length === column) {
// 如果选中的数据与层级联动数相同就完成选择
onSelectCascaderFinish(newCheckedList)
} else {
// 设置导航
let newTabList = configNav(newCheckedList, column);
setTabList(newTabList);
setActiveKey(activeKey + 1);
}
};
/* 配置渲染的列表 */
const renderCallback = (checkedList: string[], tabKey: number): objTypes => {
if (!checkedList.length || tabKey === 0) return options;
return allCascaderList.find(item => item.text === checkedList[tabKey - 1])?.children;
};
/* 当前选中信息 */
const checkSelectOption = (text: string, key: number) => {
return checkedList[key] === text;
};
/* 完成全部选择 */
const onSelectCascaderFinish = (selectValues: Array<string>) => {
setOpen(false);
setRenderCheckedText(selectValues);
// 将数据转换成value形式导出使用
const values = renderTextCallback(selectValues, allCascaderList, fileNames, 'value');
onFinish && onFinish(values);
};
/* 点击清除信息 */
const handleClear = (e: any) => {
e.stopPropagation();
onSelectCascaderFinish([]);
setCheckedList([]);
};
/* 关闭选择框 */
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
if (newOpen) {
let newTabList = configNav(checkedList, column);
setTabList(newTabList);
setActiveKey(checkedList.length ? checkedList.length - 1 : 0);
}
};
// 渲染三级选择列表
const CascaderContext = () => (
<>
{/* ------- 层级联动导航列表 ------- */}
<div className='cascader-tab-select cursor' onClick={(e) => e.stopPropagation()}>
{tabList.map((item, idx) => (
<div
onClick={() => onChange(idx)}
className={`cascader-tab-item ${activeKey === idx ? 'active' : ''} hover-color`}
key={'cascader-tab-item-' + idx}>{item}</div>
))}
</div>
{/* ----------- 层级联动选项展示 ------------- */}
<ul className='cascader-list-wrapper'>
{renderCallback(checkedList, activeKey)?.map((item: objTypes) => (
<li key={item.value} onClick={(e) => handleSelectCascader(e, item.text)}
className={`${checkSelectOption(item.text, activeKey) ? 'checked-cascader' : ''} cursor`}
>
{checkSelectOption(item.text, activeKey) ? <CheckOutlined className='checked-cascader-icon primary-color' /> : null}
<span>{item.text}</span>
</li>
))}
</ul>
</>
)
return (
<>
<Popover overlayClassName='cascader-popover-wrapper' content={<CascaderContext />}
trigger="click" placement='bottom' open={open} arrow={false}
getPopupContainer={(triggerNode: any) => triggerNode?.parentNode}
onOpenChange={handleOpenChange}>
{render ? render(renderCheckedText) :
<Cascader allowClear open={false} value={renderCheckedText} options={undefined}
placeholder={placeholder || '请选择'} displayRender={() => checkedList.join(separator)}
suffixIcon={<CaretDownOutlined />} clearIcon={<CloseCircleFilled onClick={handleClear} />}
className='cascader-render-input' style={{ width: '100%' }} />
}
</Popover>
</>
)
}
export default memo(CascaderList);
less样式:
.cascader-popover-wrapper {
width: 100%;
.ant-popover-inner {
padding: 0;
border-radius: 2px;
border: 1px solid #e2e6ed;
max-height: 300px;
font-size: 12px;
box-shadow: 0 4px 12px 0 rgba(56, 56, 56, .15);
}
}
.cascader-tab-select {
display: flex;
align-items: center;
height: 40px;
line-height: 40px;
padding-left: 36px;
color: rgba(0, 0, 0, 0.65);
border-bottom: 1px solid #f0f0f0;
.cascader-tab-item {
margin-right: 24px;
&:last-of-type {
margin-right: 0;
}
}
}
.cascader-list-wrapper {
max-height: 260px;
padding: 0 4px;
box-sizing: border-box;
overflow: auto;
li {
position: relative;
height: 30px;
line-height: 30px;
padding-left: 32px;
color: #3d4757;
margin: 4px 0;
&:not(.checked-cascader):hover {
background-color: #f2f3f5;
}
.checked-cascader-icon {
position: absolute;
left: 10px;
top: 10px;
}
}
.checked-cascader {
color: @primary-color;
background-color: #f2f3f5;
}
}
.cascader-render-input {
border-radius: 2px;
&:focus {
box-shadow: none;
}
}
使用方法:
//1. 引入组件
import CascaderList from '@/components/CascaderList';
//2. 在页面使用
<CascaderList column={3} options={options} checkedValue={value} placeholder='请选择'
onFinish={onFinish} separator=‘/’ fileNames={fileNames} render={render} />
// column 代表层级数,默认是 3
// options 代表层级联动的数据,比如上面的json数据
// checkedValue 代表选中的数据数组,是上面json数据中value的值
// separator 代表分隔符,默认是 /
// fileNames 代表json数据中的数据配置
/*
fileNames = {
text: 'text',
value: 'value',
children: 'children'
}
*/
// render 是一个回调函数,参数是当前选中的text内容,可用于自定义页面显示内容
// onFinish 是选择完成的函数,参数是当前选中的value内容
最后你把代码复制到项目中就可以使用了,相比于传统的层级联动,页面显示效果更好!开发不易,希望得到老铁们的支持,收藏点赞加分享这是对于我们分享源码的动力!