树结构转为扁平化数据,剔除重复值再转换为树结构

news/2025/2/10 14:01:02/文章来源:https://www.cnblogs.com/xiaonanxun/p/18707729

父组件:

情况一:

接口获取树结构扁平化后剔除重复值再转为正确的树结构

情况二:

接口获取扁平化数据不需要二次转换,直接对比重复值进行剔除再转为正确的树结构

结构树使用了纯CSS 多层树结构:https://blog.csdn.net/juanzhang91008/article/details/141335075

vue项目实现图片缩放与拖拽功能:https://blog.csdn.net/qq_63310300/article/details/128872535

<template>
  <div class="home" ref="back_box">
    <div class="box" draggable="true" @dragstart="dragStart($event)" @dragend="dragEnd($event)" @wheel="handlewheel" :style="`left:${elLeft}px;top:${elTop}px;width:${elWidth}px;height:${elHeight}px;`">
      <div style="display: flex; justify-content: left; align-items: center" class="text" :style="`left:${(0 * this.elWidth) / 100}px;top:${(25 * this.elHeight) / 100}px; transform:scale(${meter_zoom})`">
        <div class="tree">
          <span class="title">{{ treeData[0].name }}</span>
        </div>
        <div class="flex-column">
          <tree-node v-for="node in treeData[0].children" :key="node.id" :node="node"></tree-node>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import TreeNode from './components/TreeNode.vue';
export default {
  name: 'HomeView',
  components: {
    TreeNode
  },
  data() {
    return {
      initWidth: 0, // 父元素的宽
      initHeight: 0, // 父元素的高
      startclientX: 0,
      startclientY: 0,
      elLeft: 100,
      elTop: 0,
      zoom: 1, //缩放比例
      elWidth: 0, // 元素宽
      elHeight: 0, // 元素高
      meter_zoom: 0, // 子元素的缩放比例
      list: [
        { id: 1, parentId: 0, name: 'Node 1', isOpen: true },
        { id: 2, parentId: 1, name: 'Node 1.1', isOpen: true },
        { id: 4, parentId: 2, name: 'Node 1.1.1', isOpen: true },
        { id: 5, parentId: 2, name: 'Node 1.1.2', isOpen: true },
        { id: 3, parentId: 1, name: 'Node 1.2', isOpen: true },
        { id: 6, parentId: 3, name: 'Node 1.2.1', isOpen: true },
        { id: 9, parentId: 6, name: 'Node 1.2.1', isOpen: true },
        { id: 10, parentId: 6, name: 'Node 1.2.2', isOpen: true },
        { id: 11, parentId: 6, name: 'Node 1.2.3', isOpen: true },
        { id: 7, parentId: 3, name: 'Node 1.2.1', isOpen: true },
        { id: 7, parentId: 3, name: 'Node 1.2.1', isOpen: true }
      ], //扁平结构的数据(从接口获取)
      noRepeatList: [], //存放剔除重复值之后的数据
      treeData: [] //将扁平化数据转换为树结构
    };
  },
  created() {
    // this.list = this.treeToFlat(this.treeData, 0); // 打平数据
    this.noRepeatList = this.noRepeatList.concat(this.comparativeData()); //对比子级数据与现有数据是否存在重复值
    let tree = this.toTree(this.noRepeatList); //将扁平化数据转换为树结构
    this.treeData = tree;
    // console.log(JSON.stringify(tree));
  },
  mounted() {
    this.initBodySize();
  },
  methods: {
    toTree(items) {
      const tree = []; //存放最终的树状结构
      const itemMap = {}; //存放每个节点数据

      for (const item of items) {
        const { id } = item;
        itemMap[id] = { ...item, children: [] }; //每个节点增加一个children属性,用来存放子节点
      }

      // 遍历所有节点,将每个节点放到其父节点的children数组中
      for (const item of items) {
        const { id, parentId } = item;

        // 如果是根节点,则直接放入结果数组中
        if (parentId == null || parentId == 0) {
          tree.push(itemMap[id]);
        } else {
          // 如果不是根节点,则将当前节点放入其父节点的children数组中
          // 子元素的parentId 等于  父节点的id  itemMap[parentId] 父节点  //itemMap[id] 当前节点
          if (itemMap[parentId]) itemMap[parentId].children.push(itemMap[id]);
        }
      }

      return tree;
    },
    //对比是否存在重复数据
    comparativeData() {
      let arr = [];
      let result = this.list.map((i) => {
        let one = this.noRepeatList.findIndex((k) => k.id == i.id);
        if (one < 0) {
          arr.push(i);
        }
      });
      return arr;
    },
    // 打平函数
    treeToFlat(data, parentId, res = []) {
      data.forEach((v) => {
        res.push({
          id: v.id,
          parentId: parentId,
          name: v.name,
          isOpen: true
        });
        if (v.children && v.children.length) {
          this.treeToFlat(v.children, v.id, res);
        }
      });
      return res;
    },
    // 拖拽开始时间
    dragStart(e) {
      console.log(e);
      // 记录元素拖拽初始位置
      this.startclientX = e.clientX;
      this.startclientY = e.clientY;
    },
    // 拖拽完成事件
    dragEnd(e) {
      console.log(e);
      // 计算偏移量
      this.elLeft += e.clientX - this.startclientX;
      this.elTop += e.clientY - this.startclientY;
    },
    // 页面初始化
    initBodySize() {
      this.initWidth = this.$refs.back_box.clientWidth;
      this.initHeight = this.initWidth * (1080 / 1920);
      this.elWidth = this.initWidth * (100 / 1400); // 元素宽
      this.elHeight = this.initHeight * (100 / 50); // 元素高
      this.meter_zoom = this.elWidth / 100; // 子元素的缩放比例
    },
    // 鼠标滚轮事件
    handlewheel(e) {
      if (e.wheelDelta < 0) {
        this.zoom -= 0.05;
      } else {
        this.zoom += 0.05;
      }
      // 如果放大超过3 就限制 不能一直放大
      if (this.zoom >= 1.5) {
        this.zoom = 1.5;
        alert('已放至最大');
        return;
      }
      // 同理 缩小也是
      if (this.zoom <= 0.5) {
        this.zoom = 0.5;
        alert('已放至最小');
        return;
      }
      this.elWidth = this.initWidth * (100 / (1920 / 2)) * this.zoom;
      this.elHeight = this.initHeight * (100 / (1080 / 2)) * this.zoom;
      this.meter_zoom = this.elWidth / 100;
    }
  }
};
</script>
<style lang="scss" scoped>
* {
  margin: 0;
  padding: 0;
}
.home {
  //
  width: 1200px;
  height: 662px;
  position: relative;
  .box {
    width: 100px;
    height: 100px;
    user-select: none;
    // background: blue;
    position: absolute;
    z-index: 2;
    .text {
      width: 100px;
      height: 100px;
      transform-origin: 0 0;
      font-size: 16px;
      position: absolute;
    }
  }
}
.flex-column {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin-left: 20px;
}

.tree .title {
  display: block;
  padding: 10px 20px;
  border-radius: 10px;
  background: #0092ee;
  color: white;
  width: 150px;
  text-align: center;
  margin: 5px 0;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
  margin-right: 60px;
  position: relative;
  font-size: 14px;
  font-weight: 600;
  margin-left: 20px;
  margin-bottom: 25%;
}

.tree .title:after {
  content: '';
  width: 50px;
  height: 1px;
  background: #53a1ef;
  position: absolute;
  right: -50px;
  top: 50%;
}
</style>

子组件:

<template><!-- <div @click="node.isOpen = !node.isOpen">{{ node.name }}</div> --><div class="item"><span class="title" @click="node.isOpen = !node.isOpen">{{ node.name }}<span class="img"> <i class="z-icon-circle-plus"></i></span></span><!-- 递归调用自身组件 --><div class="flex-column" v-if="node.isOpen && node.children && node.children.length"><TreeNode v-for="childrenNode in node.children" :key="childrenNode.id" :node="childrenNode"></TreeNode></div></div>
</template>
<script>
import TreeNode from './TreeNode.vue';export default {name: 'TreeNode',components: {TreeNode},props: {node: {type: Object,default() {return {};}}},data() {return {};},watch: {node: {handler(val) {console.log(val);},deep: true,immediate: true}},methods: {}
};
</script>
<style lang="scss" scoped>
.flex-column {display: flex;flex-direction: column;align-items: flex-start;margin-left: 20px;
}.item {position: relative;display: flex;align-items: center;
}
.img {position: absolute;right: 2px;
}
.item:before {content: '';position: absolute;left: -30px;width: 1px;/*这里的高度要等于自身高度 + 节点间margin高度 */height: calc(100% + 20px);top: 0;transform: translateY(-10px);background: #53a1ef;
}.item .title:before {content: '';position: absolute;left: -30px;top: 50%;width: 30px;height: 1px;background: #53a1ef;
}.item .title {display: block;padding: 10px 20px;border-radius: 10px;background: #0092ee;color: rgb(248, 248, 248);width: 150px;text-align: center;margin: 5px 0;box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);margin-right: 60px;position: relative;font-size: 14px;font-weight: 600;
}.item .title:after {content: '';width: 50px;height: 1px;background: #53a1ef;position: absolute;right: -50px;top: 50%;
}.item .title a {text-decoration: none;color: white;font-size: 14px;font-weight: 600;
}.item .title a:hover {cursor: pointer;
}.item:first-child:before {height: calc(50% + 10px);transform: unset;top: 50%;
}.item:last-child:before {height: calc(50% + 10px);transform: unset;top: -10px;
}.item .title:not(:has(+ .flex-column)):after {display: none;
}/* 下级只有一层时,隐藏竖线 */
.item:only-of-type:before {content: '';position: absolute;left: -30px;width: 1px;/*这里的高度要等于自身高度 + 节点间margin高度 */height: calc(100% + 20px);top: 0;transform: translateY(-10px);background: white;
}// .title {
//   display: block;
//   padding: 10px 20px;
//   border-radius: 10px;
//   background: #0092ee;
//   color: white;
//   width: 150px;
//   text-align: center;
//   margin: 5px 0;
//   box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
//   margin-right: 60px;
//   position: relative;
//   font-size: 14px;
//   font-weight: 600;
//   margin-left: 20px;
// }// .title:after {
//   content: '';
//   width: 50px;
//   height: 1px;
//   background: #53a1ef;
//   position: absolute;
//   right: -50px;
//   top: 50%;
// }
</style>

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/881664.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用echarts写关系图graph

关系图是 ECharts 提供的图表类型之一,用于展示节点(实体)之间的关系。创建一个关系图涉及到定义节点(nodes)和边(links)的数据,以及配置图表的样式和行为。 echarts针对关系图提供了三种布局;layout: "none" | "circular" | "force"no…

替换word中英文““为中文双引号“”

查找内容为(")(*)("),替换内容为"\3"

振弦式渗压计:将压力转换为频率信号的高灵敏度装置 长期测量水工结构物和土体内部渗透水压力

振弦式渗压计:将压力转换为频率信号的高灵敏度装置 长期测量水工结构物和土体内部渗透水压力振弦式渗压计是一种用于感受压力并将其转换为与压力成一定关系的频率信号输出的装置。其典型结构包括压力感应膜、振弦、电磁激振与信号拾取装置、密封外壳和屏蔽电缆等组件。GEO OSx…

DeepSeek入门到精通

入门到精通推理模型 • 例如:DeepSeek-R1,GPT-o3在逻辑推理、数学推理和实时问题解决方面表现突出。 推理大模型: 推理大模型是指能够在传统的大语言模型基础上,强化推理、逻辑分析和决策能力的模型。它 们通常具备额外的技术,比如强化学习、神经符号推理、元学习等,来增…

SAP ECC 740 创建表的索引

step1:进入ABAP数据字典(se11); step2:输入表名并点击“显示”; step3:单击“索引” step4:单击“创建索引”; step5:之后进行索引的创建

this关键字---》2

三.this的用法: 1.在构造器中调用另一个构造器 (注意:该语句要在第一条,只能在构造器中使用) 2.调用成员方法: this.方法名(参数列表) 3.不能再类外部使用 4.用于访问类的属性,是有就近原则在的 总之,this可以用来访问属性、成员方法和构造器 5.this用来区分局部变量和属性 …

DeepSeek是什么?

DeepSeek是一家专注通用人工智能(AGI)的中国科技公司,主攻大模型研发与应 用。 • DeepSeek-R1是其开源的推理模型,擅长处理复杂任务且可免费商用。 AI + 国产 + 免费 + 开源 + 强大 Deepseek可以做什么? 直接面向用户或者支持开发者,提供智能对话、文本生成、语义理解…

DeepSeek特点

https://www.deepseek.com/DeepSeek是一款功能强大的AI模型,它具有多个显著的特点,以下是对DeepSeek特点的详细归纳: 一、强大的技术能力‌推理能力‌:DeepSeek在推理能力上与国际领先的模型如OpenAI的GPT-4相媲美,能够在解决数学难题、分析复杂的法律条文等方面展现出强大…

manim边学边做--通用变换

在 Manim 动画制作中,Transform、TransformFromCopy、ReplacementTransform和Restore是四个通用的对象变换动画类。 这几个类能够实现从一个对象到另一个对象的平滑过渡、复制并变换、直接替换以及状态恢复等多种效果。Transform:将一个Mobject平滑地变换为另一个Mobject Tra…

执行abp命令时,出现死循环解决方案

今天把长时间不用的ABP Cli进行升级 将原有:volo.abp.cli卸载。安装了新的 Volo.Abp.Studio.Cli。 任何执行abp new Acme.BookStore -m none --theme leptonx-lite -csf创建项目。 于是出现以下问题(Abp找不到对应的包):分析原因 本人是设置过Nuget.cofig文件中 全局包文件…

Git: submodule 子模块简明教程

序 有种情况我们经常会遇到:某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,或者你独立开发的,用于多个父项目的库。 现在问题来了:你想要把它们当做两个独立的项目,同时又想在一个项目中使用另一个。 Git 通过子模块来解决这个问题。 子模块允许你将一个 Gi…

内外网文件交换与数据共享系统:企业级跨网高效传输解决方案

一、产品介绍 产品定位:可以实现在企业内外网物理隔离情况之下,进行文件的安全交换(导入文件、刻录文件)、进行在线审批流程、实现在线审计、并对传输文件进行实施病毒查杀。 产品介绍:内外网安全文件交换系统是企业内部实现高效文件传输的管理系统: 1.支持用户通过光盘导…