树形组件

105 阅读1分钟

``

import { useCallback, useContext, useImperativeHandle, useState } from 'react';
import { SpellGroup, SpellNode, FileType } from '@/utils/api';
import './SpellTree.scss';
import React from 'react';
import { deepCopy, findGroup } from '@/utils/data';
import { sendMaterialMessage } from '@/utils/native/index';

interface GroupProps extends SpellGroup {
    isExpand?: boolean
    isHide?: boolean
}
interface NodeProps extends SpellNode {
    isHide?: boolean
}

type SpellContextInit = {
    groupItemClick?: (groupId: number, isExpand: boolean) => void
    nodeItemClick?: (node: NodeProps) => void
};

interface SpellTreeProps {
    data: GroupProps[]
}

export interface SpellTreeComponent {
    refreshTree: () => void
    query: (value: string) => void
}

const SpellFun = React.createContext<SpellContextInit>({});

function Node(props: NodeProps) {
    const classMap: Record<FileType, string> = {
        1: 'word-icon',
        2: 'image-icon',
        3: 'video-icon',
        4: 'pdf-icon',
        5: 'article-icon',
        6: 'excel-icon',
        7: 'word-icon'
    };
    const { nodeItemClick } = useContext(SpellFun);
    if (props.isHide) return null;
    return (
        <div onClick={() => nodeItemClick!(props)} className="node-item">
            <div className="node-type file-type">
                <span className={classMap[props.type] + ' icon'}></span>
            </div>
            <div className="node-content">{props.title || props.itemContent || props.itemTitle}</div>
        </div>
    );
}



function Group(props: GroupProps) {
    const [isExpand, setExpand] = useState(props.isExpand);
    const { groupItemClick: contextGroupItemClick } = useContext(SpellFun);
    const groupItemClick = useCallback(function () {
        setExpand(!isExpand);
        contextGroupItemClick!(props.id, !isExpand);
    }, [isExpand, contextGroupItemClick]);
    if (props.isHide) return null;
    return (
        
        <div className="group-wrap">
            <div onClick={groupItemClick} className="group-item">
                <div className="left-icon">
                    <span className="folder-icon icon"></span>
                </div>
                <div className="right-content">
                    <div className="top-title">{props.name}</div>
                    <div className="tip">{`${props.children.length}个话术文件夹,${props.contentList.length}条话术`}</div>
                </div>
            </div>
            <div className="sub-group-item" style={{
                display: isExpand ? '' : 'none'
            }}>
                {props.children.map(item => <Group key={item.id} {...item}></Group>)}
                {props.contentList.map(item => <Node key={item.id} {...item}></Node>)}
            </div>
        </div>
    );
}
function setNodeStatus(group:NodeProps | GroupProps, mz: boolean) {
    group.isHide = !mz;
}

// 筛选相关
function setGroupRecursionStatus(group:GroupProps, mz: boolean) {
    group.isHide = !mz;
    group.children.forEach(function (item) {
        setGroupRecursionStatus(item, mz);
    });
    group.contentList.forEach(function (item) {
        setNodeStatus(item, mz);
    });
    
}

function isMZ(item: SpellGroup | SpellNode, filterFun: (item: SpellGroup | SpellNode) => boolean) {
    let mz = filterFun(item);
    if (!mz) {
        // 如果未命中
        if ('pid' in item) {
            // 此时为 group
            item.children.forEach(function (item) {
                if (!mz) {
                    mz = isMZ(item, filterFun);
                }else {
                    isMZ(item, filterFun);
                }
            });
            item.contentList.forEach(function (item) {
                if (!mz) {
                    mz = isMZ(item, filterFun);
                }else {
                    isMZ(item, filterFun);
                }
            });
            setNodeStatus(item, mz);
            return mz;
        }
    }
    // 如果已经命中,则不用再遍历直接标记
    if ('pid' in item) {
        setGroupRecursionStatus(item, mz);
    } else {
        setNodeStatus(item, mz);
    }
    return mz;
}

function queryTreeData(treeData: SpellGroup[], filterFun: (item: SpellGroup | SpellNode) => boolean) {
    const copyTreeData = deepCopy(treeData);
    copyTreeData.forEach(function (item) {
        isMZ(item, filterFun);
    });
    return copyTreeData;
}


function SpellTree({data: oTree}: SpellTreeProps, ref: React.Ref<SpellTreeComponent> | undefined) {
    const [tree, setTree] = useState<SpellGroup[]>(oTree);
    function groupItemClick(groupId: number, isExpand: boolean) {
        const group: GroupProps = findGroup(tree, groupId)!;
        group.isExpand = isExpand;
    }
    function nodeItemClick(node: NodeProps) {
        console.log(node, 'node');
        
        sendMaterialMessage(node);
    }
    console.log('build');
    useImperativeHandle(ref, () => ({
        refreshTree: function () {
            // setTree(oTree);
        },
        query: function (value: string) {
            if (!value) {
                setTree(oTree);
                return;
            }
            const newTree = queryTreeData(tree, function (item) {
                if ('pid' in item) {
                    return item.name.includes(value);
                } 
                return item.title.includes(value);
            });
            setTree(newTree);
        }
    }));
    return (
        <SpellFun.Provider value={{
            groupItemClick,
            nodeItemClick
        }}>
            <div className="spell-tree-page">
                {
                    tree.map(item => <Group key={item.id} {...item}></Group>)
                }
            </div>
        </SpellFun.Provider>
    );
}

export default React.forwardRef(SpellTree);