与 ant-design-vue tree 组件自定义图标的相爱相杀

与 ant-design-vue tree 组件自定义图标的相爱相杀

文章目录

  • 与 ant-design-vue tree 组件自定义图标的相爱相杀
    • 小结

  1. 最近遇到了在 tree 组件中需要用到自定义 icon 图标的需求

    
    
    
    <a-tree
        draggable
        show-icon
        default-expand-all
        :tree-data="treeNodeData"
        :replaceFields="{
            children: 'children',
            title: 'label',
            key: 'id'
        }"
        @drop="handleDropTreeNode"
        @rightClick="handleRightClickNode"
    >
        <a-icon slot="switchIcon" type="caret-down"> 
    a-tree>
    
    
    

    给出的需求是:返回数据的节点 类型 是不同的,要根据不同类型加不同图标展示

    这里,我看到 tree 组件的自定义图标是通过 slots 属性或 scopedSlots 属性 (当时也没细细比较二者的区别,后者好像可以在 template 结构里通过 slot-scope 拿到 slot 匹配到的数据源) 来区分匹配的,所以就想到,拿到数据就先根据类型给每个节点加一个插槽识别属性作为 slot 匹配点, 这里使用递归插入的方式处理

    /**
    返回数据格式大致为:
    [
        {
            label: "parentNode",
            id: "parent001",
            type: "Folder",
            subType: "Folder",
            children: [
                {
                    label: "childNode",
                    id: "children001",
                    type: "Point",
                    subType: "Interval",
                    children: [
                        {
                            // ... 更多的层
                        }
                    ]
                },
            ]
        },
        {
            label: "parentNode",
            id: "parent101",
            type: "Folder",
            subType: "Folder",
            children: []
        }
    ]
    */
    // 节点类型是由 type 和 subType 组合决定的
    function recursion(dataArray){
           
        let res = [];
        dataArray.forEach(item => {
           
            item.scopedSlots = {
            icon: this.matchType(item.type, item.subType) }; // 因为是在 vue2 单组件文件中写的,所以这里有个 this
            res.push(item);
            if(!children || !item.children.length) return;
            this.recursion(item.children);
        });
        return res;
    }
    // 匹配类型返回 icon 的值
    matchType(type, subType){
           
        const typeStr = type + subType; // 用 typeStr 也可以用 switch case 匹配
        if(type === "Folder"){
            // type 为 Folder 只有这一种类型(这是与后台协商好类型匹配字段)
            return "Folder";
        }else if(typeStr === "PointInterval"){
           
            return "pointInterval";
        }
        // ... 其他 6 种类型
        // 写到这儿,突然想到 直接返回 type + subType 的值不就好了!还免得那么多判断!!!
    }
    

    傻了, 傻了 ~~~ 我去改一下项目代码,马上回来!!!

    ============================== 认真的分割线 ==============================

    // 上面的方法改成这个写法
    function recursion(dataArray){
           
        let res = [];
        dataArray.forEach(item => {
           
            item.scopedSlots = {
            icon: item.type + item.subType };
            res.push(item);
            if(!children || !item.children.length) return;
            this.recursion(item.children);
        });
        return res;
    }
    // 只要 slot 匹配上返回节点 type + subType 的值即可
    
  2. 上面的数据请求到,就递归插入一个属性 scopedSlots:{ icon: <对应的数据点类型> } 备用:

    const id = xxx.id // 就是那啥啥请求必要的参数 id
    this.getTreeNodeData({
            // 这个方法你自己封装一下 axios,在引入后再封装调用就行
        id
    }).then(res => {
           
        if(res.success){
           
            this.treeNodeData = this.recursion(res.result); // 这里递归处理一下
        }
        // other handle code ...
    }).catch( err => {
           
        // handle error message code ...
    });
    

    用官方图标测试一下:

    
    <a-tree
        draggable
        show-icon
        default-expand-all
        :tree-data="treeNodeData"
        :replaceFields="{
            children: 'children',
            title: 'label',
            key: 'id'
        }"
        @drop="handleDropTreeNode"
        @rightClick="handleRightClickNode"
    >
        <a-icon slot="switchIcon" type="caret-down"/>
        <a-icon slot="FolderFolder" type="dropbox"/>
        <a-icon slot="PointInterval" type="chrome"/>
    a-tree>
    
  3. 接下来,就是 自定义图标 了!!!

    官方给出的自定义图标的解决方案是:

    // 利用 Icon 组件封装一个可复用的自定义图标。可以通过 components 属性传入一个组件来渲染最终的图标,以满足特定的需求。
    <template>
        <div class="custom-icons-list">
            <heart-icon :style="{
               color: 'hotpink' }" />
        div>
    template>
    <script>
    const HeartSvg = {
             // 官网的小红心
        template: `
            
                
            
        `,
    };
    
    const HeartIcon = {
            
        template: `
            
        `,
        data() {
            
            return {
            
                HeartSvg,
            };
        },
    };
    
    export default {
            
        components: {
            
            HeartIcon,
        }
    };
    script>
    

    好嘞,这还没涉及插槽呢,试试能不能用 ~
    因为是基于现有项目的二次开发,ant-design-vue 的版本是 1.5.2, 不知道与版本有关系没
    好家伙,不加载图标,直接报错(大致意思就是模板编译器不可用), 如图:
    模板编译器不可用
    好嘛,那试试 jsxrender 函数

    <template>
        <a-tree
            draggable
            show-icon
            default-expand-all
            :tree-data="treeNodeData"
            :replaceFields="{
                children: 'children',
                title: 'label',
                key: 'id'
            }"
            @drop="handleDropTreeNode"
            @rightClick="handleRightClickNode"
        >
            <a-icon slot="switchIcon" type="caret-down"/>
            <a-icon slot="FolderFolder" type="dropbox"/>
            
            <heart-icon slot="PointInterval" :style="{
               color: 'hotpink', fontSize: '16px' }"/>
        a-tree>
    template>
    
    <script type="text/jsx"> // 记得加上 type="text/jsx" —— 免得一片红
        const HeartSvg = {
            
            render() {
            
                return (
                    <svg width="1em" height="1em" fill="currentColor" viewBox="0 0 1024 1024">
                        <path d="M923 283.6c-13.4-31.1-32.6-58.9-56.9-82.8-24.3-23.8-52.5-42.4-84-55.5-32.5-13.5-66.9-20.3-102.4-20.3-49.3 0-97.4 13.5-139.2 39-10 6.1-19.5 12.8-28.5 20.1-9-7.3-18.5-14-28.5-20.1-41.8-25.5-89.9-39-139.2-39-35.5 0-69.9 6.8-102.4 20.3-31.4 13-59.7 31.7-84 55.5-24.4 23.9-43.5 51.7-56.9 82.8-13.9 32.3-21 66.6-21 101.9 0 33.3 6.8 68 20.3 103.3 11.3 29.5 27.5 60.1 48.2 91 32.8 48.9 77.9 99.9 133.9 151.6 92.8 85.7 184.7 144.9 188.6 147.3l23.7 15.2c10.5 6.7 24 6.7 34.5 0l23.7-15.2c3.9-2.5 95.7-61.6 188.6-147.3 56-51.7 101.1-102.7 133.9-151.6 20.7-30.9 37-61.5 48.2-91 13.5-35.3 20.3-70 20.3-103.3 0.1-35.3-7-69.6-20.9-101.9z" />
                    </svg>
                );
            }
        };
    
        const HeartIcon = {
            
            props: {
            
                slot: String
            },
            render() {
            
                return (<a-icon component={
            this.HeartSvg} slot={
            this.slot} />) // 注意这里需要要用 this
            },
            data() {
            
                return {
            
                    HeartSvg,
                };
            },
        };
    
        export default {
            
            components: {
            
                HeartIcon,
            }
        };
    script>
    

    到这一步,自定义小爱心图标是展示出来了,页面上也没看出有什么问题,但是咱得看看还有啥问题没

    嗯,自找麻烦 ?! 来了,看图:
    与 ant-design-vue tree 组件自定义图标的相爱相杀_第1张图片
    意思是 slot 作为保留字,不能被用作组件的 prop 属性;再来尝试替换 slot props 名为 slotCont 或者任意其他名字, 直接看写法吧(其他省略了哈):

    <heart-icon slotCont="PointInterval"/>
    
    <script>
        const HeartIcon = {
            
            props: {
             slotCont: String },
            rentder(){
            
                return ( <a-icon component={
            this.HeartSvg} slot={
            this.slotCont} /> )
            },
            // data 就省略了哈,看上面就好
        }
    script>
    

    好嘛,小图标又跑了,又不显示了!!! OMG …

    好嘞,再试试