基于vue實(shí)現(xiàn)多功能樹(shù)形結(jié)構(gòu)組件的示例代碼
核心功能
- 遞歸渲染:組件可以遞歸地渲染每個(gè)節(jié)點(diǎn)及其子節(jié)點(diǎn),形成樹(shù)形結(jié)構(gòu);
- 自定義樣式:支持通過(guò)傳入的節(jié)點(diǎn)數(shù)據(jù)自定義節(jié)點(diǎn)顏色和文本;
- 動(dòng)態(tài)布局:可以根據(jù)傳入的屬性決定節(jié)點(diǎn)是左布局、右布局還是左右布局;
- 層級(jí)顏色:根據(jù)節(jié)點(diǎn)的層級(jí)顯示不同的顏色。
廢話(huà)不多說(shuō),我們直接開(kāi)始!
效果圖:

首先,我們?cè)?code>components文件夾下新建一個(gè)組件PathoNode.vue,該組件將負(fù)責(zé)渲染每個(gè)節(jié)點(diǎn)及其子節(jié)點(diǎn)。
其中,LEVEL_COLORS 為不同層級(jí)節(jié)點(diǎn)定義了顏色,isLeaf 計(jì)算屬性用于判斷當(dāng)前節(jié)點(diǎn)是否為葉子節(jié)點(diǎn)。
代碼如下:
<template>
<div class="node-item" :class="{left: isLeft}">
<div class="node-item_not-leaf" v-if="!isLeaf">
<div class="node-name" :style="{background: node.color || LEVEL_COLORS[node.level] || LEVEL_COLORS[3]}" :class="{'round': node.level === 2}">{{node.text}}</div>
<div class="node-children">
<patho-node v-for="(childNode, i) in node.children" :key="'childNode' + i" :node="childNode" :isLeft="isLeft"/>
</div>
</div>
<div class="node-item_leaf" v-else>
<div class="node-name">{{node.text}}</div>
</div>
</div>
</template>
?
<script>
const LEVEL_COLORS = {
1: '#1A4843',
2: '#464885',
3: '#46857E',
4: '#857146',
5: '#6A8546',
6: '#854646',
7: '#818076',
8: '#979B33',
9: '#336E9B',
10: '#854683'
}
export default {
name: 'PathoNode',
props: {
node: {
type: Object,
default () {
return {}
}
},
isLeft: {
type: Boolean,
default: false
}
},
data () {
return {
LEVEL_COLORS
}
},
computed: {
isLeaf () {
return !(this.node.children && this.node.children.length > 0)
}
},
methods: {
?
}
}
</script>
?
<style lang="scss" scoped>
$border-color-primary: #B5BDC4;
$text-color-primary: #b4babf;
$color-black: #000000;
@mixin firstNode {
position: relative;
border: none;
}
@mixin rightNode {
position: relative;
padding-left: 10px;
border-left: 1px solid $border-color-primary;
}
@mixin leftNode {
position: relative;
padding: 0;
padding-right: 10px;
border: none;
border-right: 1px solid $border-color-primary;
}
@mixin rightHorizonLine {
content: '';
position: absolute;
top: 50%;
left: 0;
width: 10px;
border-top: 1px solid $border-color-primary;
}
@mixin leftHorizonLine {
@include rightHorizonLine;
left: auto;
right: 0;
}
@mixin beforeRightFirstChild {
bottom: 0;
border-radius: 4px 0 0 0;
border-left: 1px solid $border-color-primary;
}
@mixin beforeRightLastChild {
left: 0;
top: 0;
bottom: 50%;
border-radius: 0 0 0 4px;
border: none;
border-left: 1px solid $border-color-primary;
border-bottom: 1px solid $border-color-primary;
}
// 左邊第一個(gè)child
@mixin beforeLeftFirstChild {
@include beforeRightFirstChild;
border: none;
border-radius: 0 4px 0 0;
border-top: 1px solid $border-color-primary;
border-right: 1px solid $border-color-primary;
}
// 左邊最后一個(gè)child
@mixin beforeLeftLastChild {
@include beforeRightLastChild;
border-radius: 0 0 4px 0;
border: none;
border-right: 1px solid $border-color-primary;
border-bottom: 1px solid $border-color-primary;
}
@mixin beforeRightOnlyOneChild {
left: 0;
top: 50%;
bottom: auto;
border: 0;
border-radius: 0;
border-top: 1px solid $border-color-primary;
}
@mixin beforeLeftOnlyOneChild {
@include beforeRightOnlyOneChild;
left: auto;
right: 0;
}
.node-item {
display: flex;
position: relative;
flex-direction: row;
justify-content: flex-start;
@include rightNode;
&::before{
@include rightHorizonLine;
}
&:first-child{
@include firstNode;
&::before{
@include beforeRightFirstChild;
}
}
&:last-child{
border-left: none;
&::before{
@include beforeRightLastChild;
}
}
&:first-child:last-child{
border-left: none;
}
&:first-child:last-child::before{
@include beforeRightOnlyOneChild;
}
.node-name{
flex-shrink: 0;
display: inline-block;
font-size: 14px;
border-radius: 1px;
margin: 10px 0;
padding: 5px 20px;
width: auto;
line-height: 20px;
font-weight: 600;
height: auto;
border-radius: 3px;
color: $color-black;
&.round{
width: 64px;
height: 64px;
padding: 0;
border-radius: 50%;
line-height: 64px;
text-align: center;
}
}
.node-children{
@include rightNode;
border-left: none;
&::before{
@include rightHorizonLine;
}
}
.node-item_not-leaf{
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
&::before{
border-left: 1px solid $border-color-primary;
}
}
.node-item_leaf{
.node-name{
background: $color-black;
color: $text-color-primary;
border: 1px solid $text-color-primary;
margin-right: 20px;
}
}
&.left{
@include leftNode;
&::before{
@include leftHorizonLine;
}
&:first-child{
@include firstNode;
}
&:first-child::before{
@include beforeLeftFirstChild;
}
&:last-child{
border: none;
&::before{
@include beforeLeftLastChild;
}
}
&:first-child:last-child{
border-right: none;
}
&:first-child:last-child::before{
@include beforeLeftOnlyOneChild;
}
.node-item_not-leaf{
&::before{
border: none;
border-right: 1px solid $border-color-primary;
}
}
.node-name{
&::after{
content: '\200E';
}
}
.node-children{
@include leftNode;
border-right: none;
&::before{
@include leftHorizonLine;
}
}
.node-item_leaf{
.node-name{
margin-right: 0;
margin-left: 20px;
}
}
}
// transition
.node-fade-enter-acitve, .node-fade-leave-active {
transition: all .5s;
}
.node-fade-enter, .node-fade-leave-to{
opacity: 0;
}
.node-fade-enter-to, .node-fade-leave {
opacity: 1;
}
}
</style>
節(jié)點(diǎn)組件部分負(fù)責(zé)定義單個(gè)節(jié)點(diǎn)的渲染結(jié)構(gòu),并通過(guò)遞歸調(diào)用<patho-node>組件處理子節(jié)點(diǎn)。當(dāng)然,根據(jù)具體業(yè)務(wù)需求,你也可以進(jìn)一步封裝此文件成為專(zhuān)用的業(yè)務(wù)組件。
<template>
<div class="patho-tab" ref="pathoChartContainer">
<div class="patho-tab__zoom" @click="clickHandler">
<el-scrollbar style="height:100%; width:100%;">
<transition name="patho-chart">
<div class="patho-chart" :style="{ transform: `scale(${scaleRatio}) translate(${translateX}px, ${translateY}px)` }">
<div class="patho-chart__section patho-chart__section_left" v-if="leftDatas.length > 0">
<patho-node v-for="(node, i) in leftDatas" :key="'node' + i" :node="node" :isLeft="true" />
</div>
<div class="patho-chart__section patho-chart__section_center root-node" v-if="nodeData">{{ nodeData.text }}
</div>
<div class="patho-chart__section patho-chart__section_right " v-if="rightDatas.length > 0">
<patho-node v-for="(node, i) in rightDatas" :key="'node' + i" :node="node" :isLeft="false" />
</div>
</div>
</transition>
</el-scrollbar>
</div>
</div>
</template>
<script>
import pathoNode from '@/components/PathoNode.vue'
export default {
components: {
pathoNode
},
data() {
return {
scaleRatio: 1,
translateX: 0,
translateY: 0,
// 組織結(jié)構(gòu)圖數(shù)據(jù)
nodeData: {
"text": "綜合分析",
"color": null,
"level": 1,
"children": [
{
"text": "血",
"color": "#9BBA5B",
"level": 2,
"children": [
{
"text": "活血化瘀",
"color": null,
"level": 3,
"children": [
{
"text": "岷歸",
"color": null,
"level": 4,
"children": null
}
]
}
]
},
{
"text": "汗",
"color": "#C58080",
"level": 2,
"children": [
{
"text": "固澀_止汗",
"color": null,
"level": 3,
"children": [
{
"text": "白芍",
"color": null,
"level": 4,
"children": null
},
{
"text": "于朮",
"color": null,
"level": 4,
"children": null
}
]
}
]
}
// ...
]
}
}
},
computed: {
leftDatas() {
const children = this.nodeData.children || []
const len = children.length
return children.slice(0, Math.floor(len / 2))
},
rightDatas() {
const children = this.nodeData.children || []
const len = children.length
return children.slice(len / 2)
}
},
methods: {
clickHandler() {
if (this.scaleRatio === 1) {
this.scaleRatio = 1.2;
} else {
this.scaleRatio = 1;
}
}
}
}
</script>
<style lang="scss" scoped>
$border-color-primary: #B5BDC4;
$color-primary: #49B8A3;
$color-black: #000000;
?
@mixin rightHorizonLine {
content: '';
position: absolute;
top: 50%;
left: 0;
width: 10px;
border-top: 1px solid $border-color-primary;
}
?
@mixin leftHorizonLine {
@include rightHorizonLine;
left: auto;
right: 0;
}
?
.patho-tab {
display: flex;
flex-direction: column;
height: 100%;
position: relative;
?
&__body {
user-select: none;
overflow: hidden;
flex-grow: 1;
}
?
.patho-chart {
position: relative;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin: 0 auto;
padding: 2em 0;
width: max-content;
cursor: pointer;
?
.patho-chart__section {
display: flex;
flex-direction: column;
flex-shrink: 0;
flex-basis: auto;
?
&.patho-chart__section_right {
position: relative;
padding: 0 0 0 10px;
?
&::before {
@include rightHorizonLine
}
}
?
&.patho-chart__section_left {
position: relative;
direction: rtl;
text-align: left;
justify-content: flex-end;
padding: 0 10px 0 0;
?
&::before {
@include leftHorizonLine;
}
}
}
?
.root-node {
width: 90px;
height: 90px;
border-radius: 50%;
background: #1A4843;
color: $color-black;
font-size: 16px;
font-weight: 600;
color: $color-primary;
text-align: center;
line-height: 90px;
}
}
}
?
/deep/ .el-scrollbar__view {
min-height: 100%;
}
?
.patho-tab__zoom {
position: fixed;
left: 0;
bottom: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
user-select: none;
z-index: 100;
?
.patho-chart {
margin: 0;
transform-origin: 0 0;
}
}
?
.patho-tab__no-datas {
margin-top: 20%;
text-align: center;
?
&_icon {
width: 200px;
}
?
&_text {
@include noDatas;
}
}
?
.patho-chart-enter-active,
.patho-chart-leave-active {
transition: opacity .5s;
}
?
.patho-chart-enter,
.patho-chart-leave-to {
opacity: 0;
}
?
.patho-chart-enter-to,
.patho-chart-leave {
opacity: 1;
}
</style>
通過(guò)以上設(shè)計(jì),我們成功打造了一個(gè)多功能的樹(shù)形結(jié)構(gòu)組件,具備豐富的自定義選項(xiàng),涵蓋節(jié)點(diǎn)顏色、文本和布局等方面。這樣的組件不僅能夠?qū)崿F(xiàn)功能性的樹(shù)形結(jié)構(gòu)展示,同時(shí)為用戶(hù)提供了生動(dòng)多彩的視覺(jué)體驗(yàn)。
以上就是基于vue實(shí)現(xiàn)多功能樹(shù)形結(jié)構(gòu)組件的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于vue多功能樹(shù)形結(jié)構(gòu)組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
VUE前端導(dǎo)出文件之file-saver插件安裝使用教程
這篇文章主要給大家介紹了關(guān)于VUE前端導(dǎo)出文件之file-saver插件安裝使用的相關(guān)資料,file-saver是一個(gè)用于保存文件的JavaScript庫(kù),它提供了一種簡(jiǎn)單的方式來(lái)生成和保存文件,支持各種文件類(lèi)型,例如文本文件、圖片、PDF等,需要的朋友可以參考下2024-05-05
基于Vue.js與WordPress Rest API構(gòu)建單頁(yè)應(yīng)用詳解
這篇文章主要介紹了基于Vue.js與WordPress Rest API構(gòu)建單頁(yè)應(yīng)用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
詳解VUE中的插值( Interpolation)語(yǔ)法
這篇文章主要介紹了詳解VUE中的插值( Interpolation)語(yǔ)法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
在Vue中使用Echarts實(shí)例圖的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于如何在Vue中使用Echarts實(shí)例圖的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
Vue父組件向子組件傳值以及data和props的區(qū)別詳解
這篇文章主要介紹了Vue父組件向子組件傳值以及data和props的區(qū)別詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Vue數(shù)據(jù)更新但頁(yè)面沒(méi)有更新的多種情況問(wèn)題及解決
這篇文章主要介紹了Vue數(shù)據(jù)更新但頁(yè)面沒(méi)有更新的多種情況問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
vue項(xiàng)目中chunk-vendors.js提示報(bào)錯(cuò)的查看方法(最新推薦)
在vue項(xiàng)目中,chunk-vendors.js報(bào)出的錯(cuò)誤提示經(jīng)常會(huì)導(dǎo)致開(kāi)發(fā)者困惑,正確查看錯(cuò)誤的方法是從錯(cuò)誤提示的詳細(xì)信息中找到報(bào)錯(cuò)原因,下面給大家分享vue項(xiàng)目中chunk-vendors.js提示報(bào)錯(cuò)的查看方法,感興趣的朋友一起看看吧2024-12-12
vue實(shí)現(xiàn)數(shù)字翻頁(yè)動(dòng)畫(huà)
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)數(shù)字翻頁(yè)動(dòng)畫(huà),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08

