react 级联菜单多选(取消父子关联)控件

28 篇文章 0 订阅
15 篇文章 0 订阅

因为本人用的React框架,貌似在各大组件库都没有看到,类似取消父子关联的一个级联菜单组件。

我这里使用了antd的基础上开发的,粘贴即用

效果图:(可能是有点不好看,见谅)
请添加图片描述
返回的数据格式:(都是把层级给你返回了,从父亲到自己的一个关系)
在这里插入图片描述

我把需要的代码全部放在一个文件里面了,直接复制就可以用

import React, {useEffect, useState} from "react";
import {Cascader, Tag} from 'antd';
import 'antd/dist/antd.css';

export interface defaultData {
    title: string;
    id?: string;
    children:
        | {
        title: string;
        id?: string;
        children: never[];
        value: string;
    }[]
        | [];
    value: string;
}

export interface PropsType {
    data: any;
    placeholder?: string;
    onChange: (res: defaultData[]) => void;
}

/**
 * 给选择的目标打上标记
 * @param optData = []
 * @param value: number
 */
function f(optData, value) {
    return optData?.map(val => {
        try {
            // 选中
            if (val.value === value) {
                val.isSelect = true;
                val.label = val.label.indexOf("√") > -1 ? val.label : `${val.label}`;
            }
            return {
                ...val,
                isSelect: !!val.isSelect, // 控制需否选中
                children: val.children?.length ? f(val.children, value) : [],
            }
        } catch (e) {
            console.log(e);
        }
    })
}

/**
 * 给未选中的目标取消标记
 * @param data = []
 * @param value: number
 */
function f1(data, value) {
    return data?.map(val => {
        try {
            if (val.value === value) {
                val.isSelect = false;
                if (val.label.indexOf('√') > -1) {
                    val.label = val.label.split(" ")[1];
                }
            }
            return {
                ...val,
                children: val.children?.length ? f1(val.children, value) : [],
            }
        } catch (e) {
            console.log(e);
        }
    })
}


/**
 * 递归查找指定数据
 * @param list = []
 * @param id: number
 */
function getCurrent(list, id) {
    for (let o of list || []) {
        if (o.value == id) return o
        const o_ = getCurrent(o.children, id)
        if (o_) return o_
    }
}

/**
 * 级联菜单多选,取消父子关联
 * @param props
 * @constructor
 */
const CascaderMui: React.FC<PropsType> = (props) => {

    const {
        options,
        placeholder,
        onChange,
        defaultValue = [],
        style = {width: '100%',},
        expandTrigger = 'hover'
    } = props;

    const [open, setOpen] = useState(false);
    const [state, setState] = useState({
        select: {}, // 选中
        noselect: {}, // 未选择
        obj: {}, // 作为tag显示
        opt: options,
    });

    Object.values(state.select)?.map(optvalue => {
        f(state.opt, optvalue[optvalue.length - 1], 'onselect')
    })

    Object.values(state.noselect)?.map(noselectvalue => {
        f1(state.opt, noselectvalue[noselectvalue.length - 1])
    })

    useEffect(() => {
        try {
            // 作为初始化需要   暂时没想到好的方法,现存本地解决下
            localStorage.setItem('o', JSON.stringify(options));

            let config = {};
            let obj = {};

            if (defaultValue.length) {
                // 有默认数据时候
                defaultValue.map(v => {

                    const index = v[v.length - 1];

                    config[index] = v;

                    obj[index] = [index, getCurrent(options, index)?.label]; // 初始回显的数据处理
                })
            }
            setState((s) => ({...s, obj, select: config, opt: f(options),}))
        } catch (e) {
            console.log(e);
        }
    }, [options])

    return (
        <Cascader
            style={style}
            placeholder={placeholder}
            options={state.opt}
            changeOnSelect
            defaultValue={state.obj}
            expandTrigger={expandTrigger}
            open={open}
            onFocus={(arg) => {
                console.log(arg)
                setOpen(true);
            }}
            onBlur={(arg) => {
                setOpen(false);
            }}
            onChange={(v, l) => {

                try {
                    // 记录每次点击的数据
                    if (l && l.length > 0) {
                        let obj = state.obj;
                        let select = state.select;
                        let noselect = state.noselect;

                        if (obj[v[v.length - 1]]) {

                            noselect[v[v.length - 1]] = v;
                            delete obj[v[v.length - 1]];
                            delete select[v[v.length - 1]];

                        } else {

                            delete noselect[v[v.length - 1]];
                            select[v[v.length - 1]] = v;
                            obj[v[v.length - 1]] = [l[l.length - 1].value, l[l.length - 1].label]
                        }

                        f(state.opt, v[v.length - 1])

                        setState((s) => ({...s, noselect, obj, select}))
                        onChange(Object.values(state.select))
                    } else {

                        setState({
                            select: {},
                            noselect: {},
                            obj: {},
                            opt: localStorage.getItem('o') && JSON.parse(localStorage.getItem('o')),
                        })
                        onChange([])//全部清空
                    }
                } catch (e) {
                    console.log(e);
                }

            }}
            displayRender={() => {
                return Object.values(state.obj).map(label => {
                    return (
                        <Tag closable
                             key={label}
                             onClose={() => {
                                 let nos = state.noselect;
                                 nos[label[0]] = state.select[label[0]];

                                 setState((s) => ({...s, noselect: nos,}))

                                 delete state.obj[label[0]];
                                 delete state.select[label[0]];

                                 onChange(Object.values(state.select));
                             }}
                        >
                            {label[1]}
                        </Tag>
                    );
                })
            }}
        />
    );
};

export default CascaderMui;

使用

import React, {useEffect, useState} from 'react';
import CascaderUi from '@/components/CascaderUi' // 这里就是上面的文件夹位置了

export const options = [
    {
        label: 'Light',
        value: 19991,
        children: new Array(20).fill(null).map((_, index) => ({
            label: `Number ${index}`,
            value: index,
            children: [],
        })),
    },
    {
        label: 'Bamboo',
        value: 'Bamboo',
        children: [
            {
                label: 'Little',
                value: 'Little',
                children: [
                    {
                        label: 'Toy Fish',
                        value: 'Toy Fish',
                        children: [],
                    },
                    {
                        label: 'Toy Cards',
                        value: 'Toy Cards',
                        children: [],
                    },
                    {
                        label: 'Toy Bird',
                        value: 'Toy Bird',
                        children: [],
                    },
                ],
            },
        ],
    },
];


export default function () {

    const [state, setState] = useState();

    useEffect(() => {
    	// 这里是模拟了接口异步 忽略就好了
        setTimeout(() => {
            setState(options)
        })
    }, [])

    const onChange = (val) => {
        console.log(val)
    }

    return (
        <CascaderUi
            options={state}
            placeholder="请选择"
            onChange={onChange}
            defaultValue={
                [
                    [19991, 0],
                    [19991],
                ]
            }
        />
    )
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小朋友120

谢谢你拉近我与星星的距离

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值