基于vue實現多功能樹形結構組件的示例代碼
核心功能
- 遞歸渲染:組件可以遞歸地渲染每個節(jié)點及其子節(jié)點,形成樹形結構;
- 自定義樣式:支持通過傳入的節(jié)點數據自定義節(jié)點顏色和文本;
- 動態(tài)布局:可以根據傳入的屬性決定節(jié)點是左布局、右布局還是左右布局;
- 層級顏色:根據節(jié)點的層級顯示不同的顏色。
廢話不多說,我們直接開始!
效果圖:
首先,我們在components
文件夾下新建一個組件PathoNode.vue
,該組件將負責渲染每個節(jié)點及其子節(jié)點。
其中,LEVEL_COLORS
為不同層級節(jié)點定義了顏色,isLeaf
計算屬性用于判斷當前節(jié)點是否為葉子節(jié)點。
代碼如下:
<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; } // 左邊第一個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; } // 左邊最后一個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é)點組件部分負責定義單個節(jié)點的渲染結構,并通過遞歸調用<patho-node>
組件處理子節(jié)點。當然,根據具體業(yè)務需求,你也可以進一步封裝此文件成為專用的業(yè)務組件。
<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, // 組織結構圖數據 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>
通過以上設計,我們成功打造了一個多功能的樹形結構組件,具備豐富的自定義選項,涵蓋節(jié)點顏色、文本和布局等方面。這樣的組件不僅能夠實現功能性的樹形結構展示,同時為用戶提供了生動多彩的視覺體驗。
以上就是基于vue實現多功能樹形結構組件的示例代碼的詳細內容,更多關于vue多功能樹形結構組件的資料請關注腳本之家其它相關文章!
相關文章
vue3-print-nb實現頁面打印(含分頁打印)示例代碼
大多數后臺系統(tǒng)中都存在打印的需求,在有打印需求時,對前端來說當然是直接打印頁面更容易,下面這篇文章主要給大家介紹了關于vue3-print-nb實現頁面打印(含分頁打印)的相關資料,需要的朋友可以參考下2024-01-01vue使用$store.commit() undefined報錯的解決
這篇文章主要介紹了vue使用$store.commit() undefined報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06