基于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: "",
                          }
                    ]
                }

 

posted @ 2024-09-21 20:25  haonanElva  阅读(244)  评论(0编辑  收藏  举报