与 ant-design-vue tree 组件自定义图标的相爱相杀
与 ant-design-vue tree 组件自定义图标的相爱相杀
-
最近遇到了在 tree 组件中需要用到自定义 icon 图标的需求
<!-- 首先,请求回来的数据是多层结构且不知道嵌套几层,所以自己多层 v-for 自然不现实 --> <!-- 官方也考虑到这个问题,只要通过 tree-data 属性直接给出数据即可,如下 --> <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> <!-- 上面的 a-tree 组件属性不清楚的,直接看 ant-design-vue 官方文档即可 -->
给出的需求是:返回数据的节点
类型
是不同的,要根据不同类型加不同图标展示这里,我看到 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 的值即可
-
上面的数据请求到,就递归插入一个属性
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 ... });
用官方图标测试一下:
<!-- ok 可行 --> <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>
-
接下来,就是 自定义图标 了!!!
官方给出的自定义图标的解决方案是:
// 利用 Icon 组件封装一个可复用的自定义图标。可以通过 component 属性传入一个组件来渲染最终的图标,以满足特定的需求。 <template> <div class="custom-icons-list"> <heart-icon :style="{ color: 'hotpink' }" /> </div> </template> <script> const HeartSvg = { // 官网的小红心 template: ` <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 = { template: ` <a-icon :component="HeartSvg" /> `, data() { return { HeartSvg, }; }, }; export default { components: { HeartIcon, } }; </script>
好嘞,这还没涉及插槽呢,试试能不能用 ~
因为是基于现有项目的二次开发,ant-design-vue
的版本是1.5.2
, 不知道与版本有关系没
好家伙,不加载图标,直接报错(大致意思就是模板编译器不可用), 如图:
好嘛,那试试jsx
跟render 函数
:<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>
到这一步,自定义小爱心图标是展示出来了,页面上也没看出有什么问题,但是咱得看看还有啥问题没
嗯,自找麻烦 ?! 来了,看图:
意思是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 …
好嘞,再试试
<template>
匹配插槽,不通过 slot 传值了:<a-tree draggable show-icon default-expand-all :tree-data="treeNodeData" :replaceFields="{ children: 'children', title: 'label', key: 'id' }" @drop="handleDropTreeNode" @rightClick="handleRightClickNode" > <template slot="PointInterval"> <heart-icon :style="{color: 'hotpink', fontSize: '16px'}"/> </template> </a-tree> <script type="text/jsx"> const HeartSvg = { // ... 小红心的 svg } const HeartIcon = { // props: { slotCont: String }, // 重要的这句可以删了 rentder(){ return (<a-icon component={this.HeartSvg} />) }, data(){ return { HeartSvg } } } </script>
好了,达到目标。正常显示,也只匹配
PointInterval
类型的位置,重要的是没有报错哇,O(∩_∩)O哈哈~ -
这就可以把
HeartIcon
抽出去作为一个组件(虽然这里本来就是一个组件),作为模块分离出去更清晰,命名为HeartIcon.jsx
(因为 jsx 语法,所以存为 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> ); } }; export default { rentder(){ return (<a-icon component={this.HeartSvg} />) }, data(){ return { HeartSvg } } }
小结
一路坎坎坷坷,一波三折啊 ~~~