一、横向
1、效果
2、代码
1)、TreeChart组件
<template> <table v-if="treeData.name"> <tr> <td :colspan="Array.isArray(treeData.children) ? treeData.children.length * 2 : 1" :class="{parentLevel: Array.isArray(treeData.children) && treeData.children.length, extend: Array.isArray(treeData.children) && treeData.children.length && treeData.extend}" > <div :class="{node: true, hasMate: treeData.mate}"> <div class="person" :class="Array.isArray(treeData.class) ? treeData.class : []" > <div class="avat"> <img :src="treeData.image_url" @contextmenu="$emit('click-node', treeData)"/> </div> <!-- <div class="name">{{treeData.name}}</div> --> </div> <div class="paeson_name">{{treeData.name}}</div> <template v-if="Array.isArray(treeData.mate) && treeData.mate.length"> <div class="person" v-for="(mate, mateIndex) in treeData.mate" :key="treeData.name+mateIndex" :class="Array.isArray(mate.class) ? mate.class : []" @click="$emit('click-node', mate)" > <div class="avat"> <img :src="mate.image_url" /> </div> <!-- <div class="name">{{mate.name}}</div> --> </div> <div class="paeson_name">{{treeData.name}}</div> </template> </div> <div class="extend_handle" v-if="Array.isArray(treeData.children) && treeData.children.length" @click="toggleExtend(treeData)"></div> </td> </tr> <tr v-if="Array.isArray(treeData.children) && treeData.children.length && treeData.extend"> <td v-for="(children, index) in treeData.children" :key="index" colspan="2" class="childLevel"> <TreeChart :json="children" @click-node="$emit('click-node', $event)"/> </td> </tr> </table> </template> <script> export default { name: "TreeChart", props: ["json"], data() { return { treeData: {} } }, watch: { json: { handler: function(Props){ let extendKey = function(jsonData){ jsonData.extend = (jsonData.extend===void 0 ? true: !!jsonData.extend); if(Array.isArray(jsonData.children)){ jsonData.children.forEach(c => { extendKey(c) }) } return jsonData; } if(Props){ this.treeData = extendKey(Props); } }, immediate: true } }, methods: { toggleExtend: function(treeData){ treeData.extend = !treeData.extend; this.$forceUpdate(); } } } </script> <style scoped> table{border-collapse: separate!important;border-spacing: 0!important;} td{position: relative; vertical-align: top;padding:0 0 50px 0;text-align: center; } .extend_handle{position: absolute;left:50%;bottom:30px; width:10px;height: 10px;padding:10px;transform: translate3d(-15px,0,0);cursor: pointer;} .extend_handle:before{content:""; display: block; width:100%;height: 100%;box-sizing: border-box; border:2px solid;border-color:#ccc #ccc transparent transparent; transform: rotateZ(135deg);transform-origin: 50% 50% 0;transition: transform ease 300ms;} .extend_handle:hover:before{border-color:#333 #333 transparent transparent;} /* .extend .extend_handle:before{transform: rotateZ(-45deg);} */ .extend::after{content: "";position: absolute;left:50%;bottom:15px;height:15px;border-left:2px solid #ccc;transform: translate3d(-1px,0,0)} .childLevel::before{content: "";position: absolute;left:50%;bottom:100%;height:15px;border-left:2px solid #ccc;transform: translate3d(-1px,0,0)} .childLevel::after{content: "";position: absolute;left:0;right:0;top:-15px;border-top:2px solid #ccc;} .childLevel:first-child:before, .childLevel:last-child:before{display: none;} .childLevel:first-child:after{left:50%;height:15px; border:2px solid;border-color:#ccc transparent transparent #ccc;border-radius: 6px 0 0 0;transform: translate3d(1px,0,0)} .childLevel:last-child:after{right:50%;height:15px; border:2px solid;border-color:#ccc #ccc transparent transparent;border-radius: 0 6px 0 0;transform: translate3d(-1px,0,0)} .childLevel:first-child.childLevel:last-child::after{left:auto;border-radius: 0;border-color:transparent #ccc transparent transparent;transform: translate3d(1px,0,0)} .node{position: relative; display: inline-block;margin: 0 1em;box-sizing: border-box; text-align: center;} .node:hover{color: #2d8cf0;cursor: pointer;} .node .person{position: relative; display: inline-block;z-index: 2;width:6em; overflow: hidden;} .node .person .avat{display: block;width:4em;height: 4em;margin:auto;overflow:hidden; background:#fff;border:1px solid #ccc;box-sizing: border-box;} .node .person .avat:hover{ border: 1px solid #2d8cf0;} .node .person .avat img{width:100%;height: 100%;} .node .person .name{height:2em;line-height: 2em;overflow: hidden;width:100%;} .node.hasMate::after{content: "";position: absolute;left:2em;right:2em;top:2em;border-top:2px solid #ccc;z-index: 1;} .node .paeson_name{transform: rotate(90deg);position: absolute; top: 68px;right: 39px;width: 88px;text-align: center;text-overflow: ellipsis; overflow: hidden; white-space: nowrap;} .landscape{transform:translate(-100%,0) rotate(-90deg);transform-origin: 100% 0;} .landscape .node{text-align: left;height: 8em;width:8em;right: 18px;} .landscape .person{position: absolute; transform: rotate(90deg);height: 4em;top:4em;left: 2.5em;} .landscape .person .avat{position: absolute;left: 0;border-radius: 2em;border-width:2px;} .landscape .person .name{height: 4em; line-height: 4em;} .landscape .hasMate{position: relative;} .landscape .hasMate .person{position: absolute; } .landscape .hasMate .person:first-child{left:auto; right:-4em;} .landscape .hasMate .person:last-child{left: -4em;margin-left:0;} </style>
2)、使用组件
引入
import TreeChart from '@/components/TreeChart'
注册
components: { TreeChart },
使用
<TreeChart :json="data" :class="{landscape: 1}" />
数组
data: { name: '一级 1', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg", class: ["rootNode"], children: [ { name: '一级 1-1', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" }, { name: '一级 1-2', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg", children: [ { name: '一级 1-1-2', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" }, { name: '一级 1-1-2', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" }, { name: '一级 1-1-3', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" } ] } ] }
样式
#app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } #app .avat { border-radius: 2em; border-width: 2px; } #app .name { font-weight: 700; }
完整代码
<template> <div id="app"> <TreeChart :json="data" :class="{landscape: 1}" /> </div> </template> <script> import TreeChart from '@/components/TreeChart' export default { name: 'app', components: { TreeChart }, data() { return { data: { name: '一级 1', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg", class: ["rootNode"], children: [ { name: '一级 1-1', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" }, { name: '一级 1-2', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg", children: [ { name: '一级 1-1-2', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" }, { name: '一级 1-1-2', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" }, { name: '一级 1-1-3', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" } ] } ] } } }, created(){ }, methods: { } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } #app .avat { border-radius: 2em; border-width: 2px; } #app .name { font-weight: 700; } </style>
二、竖向
1、效果
2、代码
1)、组件
<template> <table v-if="treeData.name"> <tr> <td :colspan="treeData.children ? treeData.children.length * 2 : 1" :class="{parentLevel: treeData.children, extend: treeData.children && treeData.extend}"> <div :class="{node: true, hasMate: treeData.mate}"> <div class="person" @click="$emit('click-node', treeData)"> <div class="avat"> <img :src="treeData.image_url" :title="treeData.name" /> <!-- <img :src="image_url" :title="treeData.name" />--> </div> <div class="name">{{treeData.name}}</div> </div> <div class="person" v-if="treeData.mate" @click="$emit('click-node', treeData.mate)"> <div class="avat"> <img :src="treeData.mate.image_url" :title="treeData.mate.name" /> <!-- <img :src="image_url" :title="treeData.mate.name" />--> </div> <div class="name">{{treeData.mate.name}}</div> </div> </div> <div class="extend_handle" v-if="treeData.children" @click="toggleExtend(treeData)"></div> </td> </tr> <tr v-if="treeData.children && treeData.extend"> <td v-for="(children, index) in treeData.children" :key="index" colspan="2" class="childLevel"> <TreeChart :json="children" @click-node="$emit('click-node', $event)"/> </td> </tr> </table> </template> <script> export default { name: "TreeChart", props: ["json"], data() { return { treeData: {}, // image_url: require('@/assets/tasly-logo03.png') } }, watch: { json: { handler: function(Props){ let extendKey = function(jsonData){ jsonData.extend = (jsonData.extend===void 0 ? true: !!jsonData.extend); if(Array.isArray(jsonData.children)){ jsonData.children.forEach(c => { extendKey(c) }) } return jsonData; } if(Props){ this.treeData = extendKey(Props); } }, immediate: true } }, methods: { toggleExtend: function(treeData){ treeData.extend = !treeData.extend; this.$forceUpdate(); } } } </script> <style scoped> table{border-collapse: separate!important;border-spacing: 0!important;} td{position: relative; vertical-align: top;padding:0 0 20px 0;text-align: center; } .extend_handle{position: absolute;left:50%;bottom:10px; width:10px;height: 10px;padding:10px;transform: translate3d(-15px,0,0);cursor: pointer;} .extend_handle:before{content:""; display: block; width:100%;height: 100%;box-sizing: border-box; border:2px solid;border-color:#ccc #ccc transparent transparent; transform: rotateZ(135deg);transform-origin: 50% 50% 0;transition: transform ease 300ms;} .extend_handle:hover:before{border-color:#333 #333 transparent transparent;} .extend .extend_handle:before{transform: rotateZ(-45deg);} .extend::after{content: "";position: absolute;left:50%;bottom:15px;height:15px;border-left:2px solid #ccc;transform: translate3d(-1px,0,0)} .extend:last-child:after{height: 0px} .childLevel::before{content: "";position: absolute;left:50%;bottom:100%;height:15px;border-left:2px solid #ccc;transform: translate3d(-1px,0,0)} .childLevel::after{content: "";position: absolute;left:0;right:0;top:-15px;border-top:2px solid #ccc;} .childLevel:first-child:before, .childLevel:last-child:before{display: none;} .childLevel:first-child:after{left:50%;height:15px; border:2px solid;border-color:#ccc transparent transparent #ccc;border-radius: 6px 0 0 0;transform: translate3d(1px,0,0)} .childLevel:last-child:after{right:50%;height:15px; border:2px solid;border-color:#ccc #ccc transparent transparent;border-radius: 0 6px 0 0;transform: translate3d(-1px,0,0)} .childLevel:first-child.childLevel:last-child::after{left:auto;border-radius: 0;border-color:transparent #ccc transparent transparent;transform: translate3d(1px,0,0)} .node{position: relative; display: inline-block;width: 13em;box-sizing: border-box; text-align: center;} .node .person{position: relative; display: inline-block;z-index: 2;width:200px; overflow: hidden;} .node .person .avat{display: block;width:4em;height: 4em;margin:auto;overflow:hidden; background:#fff;border:1px solid #ccc;box-sizing: border-box;} .node .person .avat img{width:100%;height: 100%;} .node .person .name{height:2em;line-height: 2em;overflow: hidden;width:100%;} .node.hasMate::after{content: "";position: absolute;left:2em;right:2em;top:2em;border-top:2px solid #ccc;z-index: 1;} .node.hasMate .person:last-child{margin-left:1em;} .landscape{transform: rotate(-90deg); padding:0 4em;} .landscape .node{text-align: left;height: 8em;width:8em;} .landscape .person{position: relative; transform: rotate(90deg);padding-left: 4.5em;height: 4em;top:4em;left: -1em;} .landscape .person .avat{position: absolute;left: 0;} .landscape .person .name{height: 4em; line-height: 4em;} .landscape .hasMate{position: relative;} .landscape .hasMate .person{position: absolute; } .landscape .hasMate .person:first-child{left:auto; right:-4em;} .landscape .hasMate .person:last-child{left: -4em;margin-left:0;} </style>
引入
import TreeChart from '@/components/TreeChart'
注册
components: { TreeChart },
使用
<TreeChart :json="data" :class="{landscape: landscape.length}" />
完整代码
<template> <div id="treeDiv"> <TreeChart :json="data" :class="{landscape: landscape.length}" /> </div> </template> <script> import TreeChart from '@/components/TreeChart' export default { name: 'showChart2', components: { TreeChart }, data() { return { landscape: [], data: { name: '一级 1', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg", class: ["rootNode"], children: [ { name: '一级 1-1', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" }, { name: '一级 1-2', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg", children: [ { name: '一级 1-1-2', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" }, { name: '一级 1-1-2', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" }, { name: '一级 1-1-3', image_url: "http://localhost:8888/20220407/a3214bd4-77c5-4c76-8ffc-06e7363e1419.jpg" } ] } ] } } }, created(){ }, methods: { } } </script> <style> #treeDiv { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 5px; } #treeDiv .avat { border-radius: 2em; border-width: 2px; } #treeDiv .name { font-weight: 700; } </style>
注意:要想由效果一的横向变成竖向,修改如下:
:class="{landscape: landscape.length}"