基于Vue实现动态组织结构图
最近一个项目里有个前端绘制家谱图的需求,大概是下面这个样子:
组件源码如下
<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 : []" @click="$emit('click-node', treeData)"> <div class="avat"> <img v-if="treeData.image_url" :src="treeData.image_url" /> <img v-else src="/static/user_default.png" /> </div> <div class="name">{{ treeData.name }}</div> <div class="sub_name" v-if="treeData.subName">{{ treeData.subName }}</div> </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> </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; width: 100% } 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; 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;*/ border-radius: 50%; box-sizing: border-box; } .node .person .avat img { width: 100%; height: 100%; } .node .person .name { height: 2em; line-height: 2em; overflow: hidden; width: 100%; } .sub_name{ } .node.hasMate::after { content: ""; position: absolute; left: 2em; right: 2em; top: 2em; border-top: 2px solid #ccc; z-index: 1; } /* 横板 */ .landscape { transform: translate(-100%, 0) rotate(-90deg); transform-origin: 100% 0; } .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>
使用
<TreeChart :json="treeData" @click-node="clickNode" />
数据如下
treeData: { name: '保密中心', subName: '张三', image_url: "", class: ["rootNode"], children: [ { name: '综合科室', subName: '张三', image_url: "" }, { name: '保密科室', subName: '王五', image_url: "", // mate: [{ // name: 'mate', // image_url: "https://static.refined-x.com/avat3.jpg" // }], children: [ { name: '保密局一', subName: '张三', image_url: "" }, { name: '保密局二', subName: '张三', image_url: "" }, { name: '保密局三', subName: '李四', image_url: "" } ] }, { name: '销毁科室', subName: '王麻子', image_url: "", } ] }
浩楠哥