首页 > 其他分享 >基于Vue实现动态组织结构图

基于Vue实现动态组织结构图

时间:2024-09-21 20:23:45浏览次数:8  
标签:Vue name 结构图 transform height position 动态 border left

最近一个项目里有个前端绘制家谱图的需求,大概是下面这个样子:

组件源码如下

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

 

标签:Vue,name,结构图,transform,height,position,动态,border,left
From: https://www.cnblogs.com/haonanZhang/p/18424465

相关文章

  • 基于SpringBoot+Vue+uniapp微信小程序的小说阅读器的详细设计和实现
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我成功案例代码参考数据库参考源码获取前言......
  • 基于SpringBoot+Vue+uniapp微信小程序的原创音乐小程序的详细设计和实现
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我成功案例代码参考数据库参考源码获取前言......
  • 地下城游戏--动态规划.路径问题
    地下城游戏题目五步走状态表示转移方程初始化填表顺序返回值代码题目链接题目五步走状态表示根据经验:有两种表示方式:1,以dp[i][j]为终点+巴拉巴拉2,以dp[i][j]位置为起点+巴拉巴拉。我开始写的时候用的第一种方式,结果没有表示出来,第二种方法可以。dp[i][j]表......
  • view-ui-plus iView Vue 3 table 自定义输入筛选条件
    使用自定义表格头实现筛选:为何和如何在使用view-ui-plus(iView的Vue3版本)时,发现原生的表格组件不支持自定义输入筛选条件为什么要使用自定义表格头?原生组件的限制view-ui-plus的表格组件提供了基本的功能,但在原生实现中,对于复杂的筛选条件或输入框的支持较为有限。......
  • 基于微信小程序UNIAPP+Spring Boot+Vue+MySQL的多角色用户的高校毕业生离校管理系统
    目录前言 一、技术栈二、系统功能介绍三、核心代码1、登录模块 2、文件上传模块3、代码封装前言相比于以前的传统手工管理方式,智能化的管理方式可以大幅降低学校的运营人员成本,实现了高校毕业生离校管理的标准化、制度化、程序化的管理,有效地防止了高校毕业生离......
  • 基于spring boot+vue+mysql的高校大学生评奖评优系统
    目录前言 一、技术栈二、系统功能介绍三、核心代码1、登录模块 2、文件上传模块3、代码封装前言相比于以前的传统手工管理方式,智能化的管理方式可以大幅降低学校的运营人员成本,实现了高校大学生评奖评优的标准化、制度化、程序化的管理,有效地防止了高校大学生评......
  • Win10 安装Node.js 以及 Vue项目的创建
    一、Node.js和Vue介绍1.Node.jsNode.js 是一个基于ChromeV8引擎的JavaScript运行环境。它允许你在服务器端运行JavaScript,使得你能够使用JavaScript来编写后端代码。以下是Node.js的一些关键特点:事件驱动和非阻塞I/O:Node.js使用事件驱动模型和非阻塞I/O操......
  • Vue学习笔记6:分别使用纯JavaScript和Vue的v-if 指令来有条件地渲染网页元素
    0缘起有时我们的网页需要根据需要用户的选择展示不同的页面元素。例如:<p>你喜欢哪种水果?</p><p> <label> <inputtype="radio"value="苹果"name="fruit"/> 苹果 </label></p><p> <label> <inputtype="ra......
  • 帝国CMS副页面如何实现一篇文章多个动态内容页
    信息页:调用一个模板,展示一条帝国cms的信息;副页面:调用另外一个模板,展示同一条帝国cms的信息。第一步:复制文件将e/action/ShowInfo.php文件复制一份,并重命名,比如命名为MoreInfo.php第二步:修改文件打开刚才复制并重命名的文件,并进行修改(1)找到//moreport和//缓存,大约在3......
  • springboot+vue在线动漫信息平台【开题+程序+论文】
    系统程序文件列表开题报告内容研究背景随着互联网的飞速发展,动漫文化在全球范围内迅速普及,成为年轻人娱乐与社交的重要载体。传统动漫传播方式受限于时间与空间,已难以满足日益增长的动漫爱好者需求。在线动漫信息平台作为连接创作者与观众的桥梁,不仅能够提供丰富多样的动漫......