vue树状显示图

转载自:https://www.cnblogs.com/steamed-twisted-roll/p/10731862.html

在这里插入图片描述

子组件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() {
  },

  watch: {
    isDetail: function(val) { // 是否是详情,详情不能添加编辑
      this.isDetail = val;
    },
    json: {
      // 遍历当前的数据
      handler: function(Props) {
        let extendKey = function(jsonData) {
          jsonData.extend =
            jsonData.extend === void 0 ? true : !!jsonData.extend;
          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) {
      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>

m;
// height: 4em;
top:35px;
left: 12px;
width: 110px;
}
}

.el-popover {
.el-button {
padding: 8px !important;
margin-left: 5px !important;
float: left;
}
}


##### 父组件tree.vue

```javascript
<template>
  <div>
    <div style="margin: 0 auto; width: 75vw; text-align: center">
      <TreeChart
        style="margin: 20px auto"
        :json="treeData"
        :class="{ landscape: isVertical }"
        :isDetail="isDetail"
        @add="addStock"
        @delete="deleteStock"
      />
    </div>

    <el-dialog
      title="提示"
      :visible.sync="dialogVisible"
      :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="类型">
              <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 "./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 = 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) {
        this.currentTreeData.partnerType = data.partnerType;
        this.currentTreeData.partnerName = data.partnerName;
        this.currentTreeData.proportionShares = data.proportionShares;
        // 前端编辑数据
        this.$message({
          type: "success",
          message: "成功",
        });
        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: "成功",
        });
        loading.close();
      }
    },
  },
};
</script>

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue树状数组扁平化是一个操作,用于将树状数组(也称为B树或二叉搜索树)中的数据结构扁平化为线性数据结构。这个操作通常用于数据展示和性能优化。 在Vue树状数组扁平化过程中,需要遍历树状数组,并将每个节点转换为对应的线性数据结构元素。这通常可以通过递归实现。 下面是一个简单的Vue树状数组扁平化的实现步骤: 1. 定义一个数据结构来表示树状数组中的节点。每个节点包含一个数据值和指向其子节点的指针。 2. 定义一个递归函数,用于遍历树状数组并转换每个节点。该函数需要接受当前节点的值和指针,以及一个空列表(用于存储扁平化的结果)。 3. 在函数中,检查当前节点是否有子节点。如果有,递归调用函数自己处理子节点。否则,将当前节点的值添加到结果列表中。 4. 最后,遍历完成后,将结果列表转换为一个普通的数组或其他适当的线性数据结构,并将其传递给Vue组件进行展示。 需要注意的是,Vue树状数组扁平化操作可能会对性能产生一定的影响,特别是在处理大型数据集时。因此,在实现过程中需要考虑性能优化措施,例如使用分页或懒加载等技术来减少不必要的计算和渲染。 此外,为了更好地使用Vue树状数组扁平化,你可能需要使用Vue的数据绑定机制和组件系统来动态地呈现扁平化的数据结构。你可以创建一个组件来显示扁平化的数据,并使用Vue的数据绑定语法将其与数据源进行绑定。 总之,Vue树状数组扁平化是一个常用的操作,可以帮助你更有效地处理树状数组数据,并提高性能和用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值