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

首先,我們在components文件夾下新建一個(gè)組件PathoNode.vue,該組件將負(fù)責(zé)渲染每個(gè)節(jié)點(diǎn)及其子節(jié)點(diǎn)。
其中,LEVEL_COLORS 為不同層級節(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),并通過遞歸調(diào)用<patho-node>組件處理子節(jié)點(diǎn)。當(dāng)然,根據(jù)具體業(yè)務(wù)需求,你也可以進(jì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>
通過以上設(shè)計(jì),我們成功打造了一個(gè)多功能的樹形結(jié)構(gòu)組件,具備豐富的自定義選項(xiàng),涵蓋節(jié)點(diǎn)顏色、文本和布局等方面。這樣的組件不僅能夠?qū)崿F(xiàn)功能性的樹形結(jié)構(gòu)展示,同時(shí)為用戶提供了生動多彩的視覺體驗(yàn)。
以上就是基于vue實(shí)現(xiàn)多功能樹形結(jié)構(gòu)組件的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于vue多功能樹形結(jié)構(gòu)組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Vue CLI 3.0腳手架如何mock數(shù)據(jù)
這篇文章主要介紹了詳解Vue CLI 3.0腳手架如何mock數(shù)據(jù),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-11-11
vue3-print-nb實(shí)現(xiàn)頁面打印(含分頁打印)示例代碼
大多數(shù)后臺系統(tǒng)中都存在打印的需求,在有打印需求時(shí),對前端來說當(dāng)然是直接打印頁面更容易,下面這篇文章主要給大家介紹了關(guān)于vue3-print-nb實(shí)現(xiàn)頁面打印(含分頁打印)的相關(guān)資料,需要的朋友可以參考下2024-01-01
vue使用$store.commit() undefined報(bào)錯的解決
這篇文章主要介紹了vue使用$store.commit() undefined報(bào)錯的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06

