最近遇到了在 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 的值即可
上面的数据请求到,就递归插入一个属性 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>
接下来,就是 自定义图标 了!!!
官方给出的自定义图标的解决方案是:
// 利用 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
, 不知道与版本有关系没
好家伙,不加载图标,直接报错(大致意思就是模板编译器不可用), 如图:
好嘛,那试试 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 …
好嘞,再试试 匹配插槽,不通过 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
}
}
}
解决方案示例:jsx + render function 解决方案 - github
一路坎坎坷坷,一波三折啊 ~~~