vue-tree 组织架构图/树形图自动生成(含添加、删除、修改)

vue

这个组件是在一个githup项目上增加了一些功能

原项目地址:https://github.com/tower1229/Vue-Tree-Chart

建议把整个安装包下载下来,写成组件使用.这样方便定制自己的业务需求

原项目效果图

 

 

 

修改后效果图,主要是增加了添加,编辑,删除功能.以及样式上的修改

添加,修改的弹框是用的element的<el-popover></el-popover>组件

 

 

因为这个组件是递归组件,所以组件里不要除了组件代码,不要把其他代码写在里面.之前我在组件里添加了一个<el-dialog></el-dialog>弹框组件.这就导致渲染出来的页面里有很多弹框

 

子组件: TreeData.vue

<template>

<table v-if="treeData && treeData.partnerName">

<tr>

<td :colspan="treeData.childers ? treeData.childers.length * 2 : 1" :class="{parentLevel: treeData.childers, extend: treeData.childers && treeData.childers.length && treeData.extend}">

<div :class="{node: true, hasMate: treeData.mate}">

<div class="person" @click="$emit(\'click-node\', treeData)">

<el-popover

v-if="!isDetail"

placement="top"

width="180"

trigger="hover">

<div style="margin: 0">

<el-button size="mini" type="primary" @click="addStock(0)" v-if="treeData.partnerType !== 1 && treeData.partnerType !== 3">添加</el-button>

<el-button type="primary" size="mini" @click="addStock(1)" v-if="treeData.proportionShares">编辑</el-button>

<el-button type="primary" size="mini" @click="deleteStock" v-if="treeData.proportionShares">删除</el-button>

</div>

<div class="avat" :class="{parent: !treeData.proportionShares, company: Number(treeData.partnerType) === 2, other: Number(treeData.partnerType) === 3}" slot="reference">

{{treeData.partnerName}}({{treeData.proportionShares ? treeData.proportionShares : 100}}%)

</div>

</el-popover>

<div class="avat" :class="{parent: !treeData.proportionShares, company: Number(treeData.partnerType) === 2, other: Number(treeData.partnerType) === 3}" v-else>

{{treeData.partnerName}}({{treeData.proportionShares}}%)

</div>

</div>

</div>

<div class="extend_handle" v-if="treeData.childers && treeData.childers.length" @click="toggleExtend(treeData)"></div>

</td>

</tr>

<!-- 这是一个递归组件,注意,这里还要调用,需要传递的数据这里也要传递,否则操作时拿不到子级的数据 -->

<tr v-if="treeData.childers && treeData.childers.length && treeData.extend">

<td v-for="(childers, index) in treeData.childers" :key="index" colspan="2" class="childLevel">

<TreeChart

:json="childers"

:isDetail="isDetail"

@add="$emit(\'add\', $event)"

@delete="$emit(\'delete\', $event)"

@click-node="$emit(\'click-node\', $event)"/>

</td>

</tr>

</table>

</template>

<script>

export default {

name: "TreeChart",

props: {

json: {}, // 渲染数据

isDetail: {

default: false // 是否是详情

}

},

data() {

return {

treeData: {},

};

},

created() {

// console.log(this.json)

},

watch: {

isDetail: function(val) { // 是否是详情,详情不能添加编辑

this.isDetail = val;

},

json: {

// 遍历当前的数据

handler: function(Props) {

let extendKey = function(jsonData) {

jsonData.extend =

jsonData.extend === void 0 ? true : !!jsonData.extend;

// if (Array.isArray(jsonData.children) && jsonData.children.length) {

// jsonData.children.forEach(c => {

// extendKey(c);

// });

// }

return jsonData;

};

if (Props) {

this.treeData = extendKey(Props);

}

},

immediate: true,

deep: true

}

},

methods: {

toggleExtend(treeData) {

treeData.extend = !treeData.extend;

this.$forceUpdate();

},

// 新增编辑股东,val: 0 新增, 1 编辑

addStock(val) {

// console.log(this.treeData)

this.$emit(\'add\', {val: val, data: this.treeData})

},

// 删除股东

deleteStock() {

this.$emit(\'delete\', this.treeData)

}

}

};

</script>

<style lang="less">

table{border-collapse: separate!important;border-spacing: 0!important;}

td{position: relative; vertical-align: top;padding:0 0 50px 0;text-align: center; }

.parent {

background: #199ed8 !important;

font-weight: bold;

}

.extend_handle{position: absolute;left:50%;bottom:27px; 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;box-sizing: border-box; text-align: center;padding: 0 5px;}

.node .person{padding-top: 15px; position: relative; display: inline-block;z-index: 2;width:120px; overflow: hidden;}

.node .person .avat{

padding: 5px;

padding-top: 10px;

display: block;width:100%;height: 100%;margin:auto;word-break: break-all; background:#ffcc00;box-sizing: border-box;border-radius: 4px;

.opreate_icon {

display: none;

}

&:hover {

.opreate_icon {

display: block;

position: absolute;

top: -3px;

right: -3px;

padding: 5px;

}

}

&.company {

background:#199ed8;

}

&.other {

background:#ccc;

}

}

.node .person .avat img{cursor: pointer;}

.node .person .name{height:2em;line-height: 2em;overflow: hidden;width:100%;}

.node.hasMate::after{content: "";position: absolute;left:2em;right:2em;top:15px;border-top:2px solid #ccc;z-index: 1;}

.node.hasMate .person:last-child{margin-left:1em;}

.el-dialog__header {

padding: 0;

padding-top: 30px;

margin: 0 30px;

border-bottom: 1px solid #F1F1F1;

text-align: left;

.el-dialog__title {

font-size: 14px;

font-weight: bold;

color: #464C5B;

line-height: 20px;

}

}

.tips {

padding: 0 20px;

.el-select {

width: 100%;

}

.blue {

color: #00B5EF;

}

.check {

margin-left: 100px;

}

.inquiry {

font-weight: bold;

}

.el-form-item__label {

display: block;

float: none;

text-align: left;

}

.el-form-item__content {

margin-left: 0;

}

}

.el-dialog__body {

padding: 30px 25px;

p {

margin-bottom: 15px;

}

}

.el-dialog__headerbtn {

top: 30px;

right: 30px;

}

// 竖向

.landscape {

transform: translate(-100%,0) rotate(-90deg);

transform-origin: 100% 0;

.node{text-align: left;height: 8em;width:8em;}

.person{

position: relative;

transform: rotate(90deg);

// padding-left: 4.5em;

// height: 4em;

top:35px;

left: 12px;

width: 110px;

}

}

.el-popover {

.el-button {

padding: 8px !important;

margin-left: 5px !important;

float: left;

}

}

</style>

父组件调用 tree.vue

<template>

<div>

<TreeChart

:json="treeData"

:class="{landscape: isVertical}"

:isDetail="isDetail"

@add="addStock"

@delete="deleteStock"

/>

<el-dialog

title="提示"

:visible.sync="dialogVisible"

@close="clearDialog"

:close-on-click-modal="false"

width="500px">

<div class="tips">

<el-form :model="ruleForm" :rules="rules" ref="ruleForm" class="demo-ruleForm">

<el-form-item label="类型" prop="type">

<el-select v-model="ruleForm.type" placeholder="类型" @change="changeType">

<el-option

v-for="item in shareholderTypeOptions"

:key="item.value"

:label="item.labelZh"

:value="item.value">

</el-option>

</el-select>

</el-form-item>

<el-form-item label="姓名" prop="partnerName">

<el-input placeholder="输入姓名" :maxlength="32" v-model="ruleForm.partnerName"></el-input>

</el-form-item>

<el-form-item label="占比" prop="proportionShares">

<el-input placeholder="输入占比" :maxlength="5" v-model="ruleForm.proportionShares"></el-input>

</el-form-item>

</el-form>

</div>

<span slot="footer" class="dialog-footer">

<div class="tip-left">

<el-button type="info" @click="dialogVisible=false">取消</el-button>

<el-button type="primary" @click="confirm">确定</el-button>

</div>

</span>

</el-dialog>

<!-- 删除提示弹框 -->

<el-dialog

title="提示"

:visible.sync="dialogVisible2"

width="30%">

<div class="tips">

<p style="text-align: left">确定删除该股东信息?</p>

</div>

<span slot="footer" class="dialog-footer">

<div class="tip-left">

<el-button type="info" @click="dialogVisible2=false">取消</el-button>

<el-button type="primary" @click="confimdelete">确定</el-button>

</div>

</span>

</el-dialog>

</div>

</template>

<script>

import TreeChart from \'@/components/TreeData\'

import { Loading } from "element-ui";

export default {

name: \'tree\',

components: {

TreeChart

},

data() {

return {

treeData: {

partnerName: \'大米科技公司\',

proportionShares: \'100\',

partnerType: 2,

id: 1,

childers: [{

partnerName: \'股东1\',

proportionShares: \'50\',

partnerType: 1,

id: 2,

partnerCode: 1

}, {

partnerName: \'股东2\',

proportionShares: \'20\',

partnerType: 1,

id: 4,

partnerCode: 1

}, {

partnerName: \'股东3\',

proportionShares: \'20\',

partnerType: 2,

id: 5,

partnerCode: 1

}, {

partnerName: \'其他\',

proportionShares: \'10\',

partnerType: 3,

id: 6,

partnerCode: 1

}]

},

isVertical: false, // 是否是竖方向,只给最外层的添加

isDetail: false, // 是否是详情,不可编辑操作

dialogVisible: false, // 添加股东弹框

dialogVisible2: false, // 删除提示弹框

ruleForm: {

type: 1,

partnerName: "",

proportionShares: null

},

rules: {

proportionShares: [

{ required: true, message: \'请输入比例\', trigger: "blur" }

],

partnerName: [

{ required: true, message: "请输入股东名称", trigger: "blur" }

],

cardId: [

{ required: true, message: "请输入证件号", trigger: "blur" }

],

type: [

{ required: true, message: "请选择类型", trigger: "blur" }

]

},

shareholderTypeOptions: [

{

labelEn: "Individual",

labelZh: "个人",

value: 1

},

{

labelEn: "Company",

labelZh: "公司",

value: 2

},

{

labelEn: "Other",

labelZh: "其他",

value: 3

}

], // 股东类型

lastId: 11, // 最后一级id

currentTreeData: {}

}

},

methods: {

// 新增编辑股东,val: 0 新增, 1 编辑

addStock(data) {

// console.log(data)

if (data.val) {

// 不使用=赋值,内存相同,改变后,treeData数据也会改变

// this.ruleForm = data.data;

this.ruleForm = Object.assign(this.ruleForm, data.data);

this.ruleForm.type = data.data.partnerType;

}

this.isEdit = data.val

// 使用=赋值,编辑时改变currentTreeData, 源数据treeData也会改变

this.currentTreeData = data.data

this.dialogVisible = true;

},

// 删除

deleteStock(data) {

// console.log(data)

this.currentTreeData = data

this.dialogVisible2 = true

},

// 确定删除

confimdelete() {

// 前端删除 遍历原数据,删除匹配id数据

const deleteData = (data) => {

data.some((item, i) => {

if (item.id === this.currentTreeData.id) {

data.splice(i, 1)

return

} else if (item.childers) {

deleteData(item.childers)

}

})

}

let arr = [this.treeData]

deleteData(arr)

this.treeData = arr[0] ? arr[0] : {}

// console.log(this.treeData)

this.dialogVisible2 = false

this.$message({

type: "success",

message: "成功"

});

},

// 保存添加股东

confirm() {

let loading = Loading.service();

this.$refs.ruleForm.validate(valid => {

if (valid) {

this.sendData();

} else {

loading.close();

}

});

},

// 发送添加股东数据

sendData() {

let loading = Loading.service();

let data = {

partnerType: this.ruleForm.type,

partnerName: this.ruleForm.partnerName,

proportionShares: this.ruleForm.proportionShares

};

if (this.isEdit) { // 编辑

// data.id = this.treeData.id;

this.currentTreeData.partnerType = data.partnerType

this.currentTreeData.partnerName = data.partnerName

this.currentTreeData.proportionShares = data.proportionShares

// 前端编辑数据

this.$message({

type: "success",

message: "成功"

});

this.clearDialog();

loading.close()

} else { // 添加

// 前端添加数据,需要自己生成子级id,可以传数据的时候把最后一级id传过来,进行累加

data.id = this.lastId ++

data.partnerCode = this.currentTreeData.id

data.extend = true

const render = (formData) => {

formData.some(item => {

if (item.id === this.currentTreeData.id) {

if (item.childers) {

item.childers.push(data)

} else {

this.$set(item, \'childers\', [data])

}

return

} else if (item.childers) {

render(item.childers)

}

})

}

let arr = [this.treeData]

render(arr)

this.treeData = arr[0]

this.$message({

type: "success",

message: "成功"

});

this.clearDialog();

loading.close()

}

},

}

}

</script>

 git-hup地址: https://github.com/shengbid/vue-demo  这个文件是平时练习的项目,里面还有一些我写的其他博客的源码,有需要可以下载看看

以上是 vue-tree 组织架构图/树形图自动生成(含添加、删除、修改) 的全部内容, 来源链接: utcz.com/z/375226.html

回到顶部