vue纯手写思维导图,拒绝插件(cv即用)

news2024/11/15 6:55:44

vue纯手写思维导图,拒绝插件(cv即用)

已完成功能点:折叠、放大、缩小、移动
后续增加功能点:添加、删除

先看结果:

在这里插入图片描述

有这么个需求,按照层级关系,把表格放在思维导图上,我第一时间想到用插件,但是找了好久都没有找到比较合适的插件,决定自己手写一个。

第一步:

理论猜想

模拟一个带有层级关系的数据格式,并且可以在vue组件中需要做成组件递归形势,左侧父级永远包含右侧子集。左侧A盒子,右侧F盒子用flex布局B/C/D竖着排列,右侧3个div分别用伪元素分别做3根横线,F盒子设置border-left 竖线,这样一拼接就感觉像是一个思维导图了,理论先这样,但是还没有想到B盒子的左侧横线和F盒子竖线交叉之后,上面圆圈多余的部分怎么去除。先动手再说,碰到问题再想着怎么处理问题。
在这里插入图片描述

第二步:

动手实践

模拟数据: 设置listCache 模拟数据 带有层级关系的格式,id是唯一的,这样做为了后期可能操作表格的时候方便找到唯一的表格。
递归组件: mindItem.vue里面的name属性名称设置 mindItem,然后再mindItem.vue组件里面再次引入<mindItem :list="item.children"></mindItem>即可递归
多余线段去除: 刚开始的做法是直接设置子集的border-left,这样的问题会造成有线段空出来,显得很多余,转换一个思路。

  • 设置B和A的连接:请看图2 把第一个div的伪元素::after设置border-left: solid 2px blue;height: 50%;bottom: 0; 这样做是让线段1向下展示,高度只有B盒子的一半,这样就感觉像线段拐弯了,从A连接到B的样式,其实是多个线段拼接起来而已。
  • 设置D和A的连接:请看图2 把第最后一个div的伪元素::after设置border-left: solid 2px #000; height: 50%; top: 0; 这样做是让线段3向上展示,高度只有C盒子的一半,这样就感觉像线段拐弯了,从A连接到C的样式,其实是多个线段拼接起来而已。
  • 设置C和A的连接:请看图2 把中间div的伪元素::after设置border-left: solid 2px yellowgreen; height: 100%; 处在中间地段的div盒子不必考虑线段拐弯问题,高度100%就行了和上下的盒子的线段连接起来就好了

图2:

在这里插入图片描述


src/views/mind/components/mindItem.vue

<template>
  <transition name="el-zoom-in-center">
    <div class="warps">
      <template v-for="(item, i) in list">
        <div
          :key="i"
          class="bodyDefault"
          :class="[
            item.first ? 'bodyOuter' : '',
            i === 0 ? 'bodyFirst' : list.length - 1 === i ? 'bodyLast' : '',
          ]"
        >
          <i
            v-if="!item.first"
            class="iconremove"
            :class="[
              !item.isExpandBefore
                ? 'el-icon-remove-outline'
                : 'el-icon-circle-plus-outline',
            ]"
            type="primary"
            @click="expendBefore(item)"
          >
          </i>
          <div class="listTable" v-show="!item.isExpandBefore">
            <el-table :data="item.tableData" style="width: 300px" border size="small">
              <el-table-column prop="name" label="姓名" align="center"> </el-table-column>
              <el-table-column prop="age" label="年龄" align="center"> </el-table-column>
            </el-table>
          </div>
          <i
            v-if="item.children && !item.isExpandBefore"
            class="iconremove"
            :class="[
              !item.isExpandAfter
                ? 'el-icon-remove-outline'
                : 'el-icon-circle-plus-outline',
            ]"
            @click="expendAfter(item)"
          >
          </i>
          <div
            v-if="item.children && !item.isExpandAfter && !item.isExpandBefore"
            class="box transition-box"
          >
            <mindItem :list="item.children"></mindItem>
          </div>
        </div>
      </template>
    </div>
  </transition>
</template>

<script>
import { expendfn } from "./index.js";
export default {
  name: "mindItem",
  components: {},
  props: {
    list: {
      type: Array,
      default: [],
    },
  },
  data() {
    return {};
  },
  computed: {},
  watch: {
    list: {
      deep: true,
      handler(newVal) {
        this.list = newVal;
      },
    },
  },
  created() {},
  mounted() {},
  methods: {
    expendBefore(val) {
      val.isExpandBefore = !val.isExpandBefore;
      this.$forceUpdate();
      console.log("后-expendBefore", val);
    },
    expendAfter(val) {
      val.isExpandAfter = !val.isExpandAfter;
      this.$forceUpdate();
      console.log("前-expendAfter", val);
    },
  },
};
</script>

<style scoped lang="less">
.warps {
  & > .bodyOuter,
  & > .bodyFirst,
  & > .bodyLast,
  & > .bodyDefault {
    padding: 10px 0 10px 24px;
    position: relative;
    border-left: none;
    .listTable {
      display: inline-block;
      display: flex;
      align-items: center;
      .expend {
        width: 10px;
        height: 100%;
        // border: 1px solid blue;
      }
    }
    display: flex;
    align-items: center;

    .box {
      flex: 1;
      margin-left: 30px;
      display: inline-block;
      position: relative;
    }
    .box::before {
      content: "";
      width: 30px;
      border: solid 1px skyblue;
      white-space: nowrap;
      display: inline-block;
      position: absolute;
      left: -15px;
      top: 50%;
      transform: translate(-50%, -50%);
    }
  }

  & > .bodyFirst::before,
  & > .bodyOuter::before,
  & > .bodyLast::before,
  & > .bodyDefault::before {
    content: "→";
    width: 30px;
    letter-spacing: 2px;
    white-space: nowrap;
    display: inline-block;
    position: absolute;
    left: 0px;
  }

  // 横线
  .bodyDefault::before {
  }
  .bodyFirst::before {
  }
  .bodyLast::before {
  }
  .bodyFirst::before {
  }

  .bodyOuter::before {
    content: "";
    border: solid 1px transparent;
  }

  // 竖线
  & > .bodyFirst::after,
  & > .bodyDefault::after,
  & > .bodyOuter::after,
  & > .bodyLast::after {
    content: "";
    width: 2px;
    height: 50%;
    border-left: solid 2px transparent;
    white-space: nowrap;
    display: inline-block;
    position: absolute;
    left: 0px;
  }

  & > .bodyDefault::after {
    border-left: solid 2px red;
    height: 100%;
  }

  & > .bodyFirst::after {
border-left: solid 2px yellowgreen;height: 50%;bottom: 0;
  }

  & > .bodyLast::after {
    border-left: solid 2px blue;height: 50%;top: 0;
  }

  // 外层
  .bodyOuter::after {
    border-left: solid 2px transparent;
  }
  // 最外层无线条
  .bodyOuter {
    background: transparent;
    border-left: 2px solid transparent;
    &.box::before {
      background: transparent;
    }
  }
  .bodyOuter::before {
    background: transparent;
  }
}

.iconremove {
  color: #409eff;
  width: 22px;
  font-size: 20px;
  cursor: pointer;
}
</style>

src/views/mind/mind.vue


<template>
  <div class="warp">
    <div class="header">
      <div>
        <el-button type="primary" size="small" @click="expendAll">展开所有</el-button>
      </div>
      <div>
        <el-input-number
          v-model="num"
          :precision="2"
          :step="0.1"
          :max="2"
          :min="0"
          style="width: 100px"
          size="mini"
          controls-position="right"
          @change="numberChange"
        >
        </el-input-number></div>
      <div>
        <el-button
          :type="isRank ? 'primary' : ''"
          icon="el-icon-rank"
          circle
          @click="rankfn"
        >
        </el-button>
      </div>
    </div>

    <div class="mind" :class="{ mindRank: isRank }" v-drag ref="refresh">
      <mindItem :list="list" :style="'transform: scale(' + num + ')'"></mindItem>
    </div>
  </div>
</template>

<script>
import mindItem from "./components/mindItem.vue";
import { expendfn } from "./components/index.js";
export default {
  name: "",
  props: {},
  components: { mindItem },
  data() {
    return {
      isRank: false,
      list: [],
      num: 1,
      listCache: [
        {
          id: 11,
          first: true,
          tableData: [
            { id: 112, name: "李四 1级-1", age: 2 },
            { id: 113, name: "李四 1级-2", age: 4 },
          ],
          children: [
            {
              parent: 11,
              id: 21,
              tableData: [
                { id: 122, name: "李四 2级-1", age: 30 },
                { id: 123, name: "李四 2级-2", age: 34 },
              ],
            },
            {
              parent: 11,
              id: 22,
              tableData: [
                { id: 124, name: "李四 2级-3", age: 65 },
                { id: 125, name: "李四 2级-4", age: 23 },
              ],
            },
            {
              parent: 11,
              id: 23,
              tableData: [
                { id: 126, name: "李四 2级-5", age: 45 },
                { id: 127, name: "李四 2级-6", age: 25 },
              ],
              children: [
                {
                  parent: 23,
                  id: 33,
                  tableData: [
                    { id: 128, name: "李四 3级-1", age: 32 },
                    { id: 129, name: "李四 3级-2", age: 623 },
                  ],
                },
                {
                  parent: 23,
                  id: 34,
                  tableData: [
                    { id: 130, name: "李四 3级-3", age: 623 },
                    { id: 131, name: "李四 3级-4", age: 256 },
                  ],
                },
                {
                  parent: 23,
                  id: 35,
                  tableData: [
                    { id: 132, name: "李四 3级-5", age: 352 },
                    { id: 133, name: "李四 3级-6", age: 2345 },
                  ],
                },
                {
                  parent: 23,
                  id: 36,
                  tableData: [
                    { id: 134, name: "李四 3级-7", age: 35 },
                    { id: 135, name: "李四 3级-8", age: 4124 },
                  ],
                },
              ],
            },
          ],
        },
      ],
    };
  },
  computed: {},
  watch: {
    num(newVal, oldVal) {
      console.log(newVal, oldVal);
      if (newVal < oldVal && oldVal <= 0.5) {
        this.num = 0.5;
      }
    },
  },
  directives: {
    drag: {
      bind: function (el) {
        let odiv = el;

        let moveing = false;
        let moves = false;
        odiv.onmousedown = (e) => {
          let arr = Array.from(odiv.classList);
          if (!arr.includes("mindRank")) return;
          let disX = e.clientX - odiv.offsetLeft;
          let disY = e.clientY - odiv.offsetTop;
          document.onmousemove = (e) => {
            let left = e.clientX - disX;
            let top = e.clientY - disY;
            if (top <= 80 && left <= 300) {
              // top = 80;
              // left = 300;
            }

            odiv.style.left = left + "px";
            odiv.style.top = top + "px";
            moveing = true;
          };

          document.onmouseup = (e) => {
            document.onmousemove = null;
            document.onmouseup = null;

            moveing = false;
          };
        };
      },
    },
  },
  created() {},
  mounted() {
    this.init();
  },
  methods: {
    rankfn() {
      this.isRank = !this.isRank;
    },
    numberChange() {
      console.log(" this.num--", this.num);
    },
    init() {
      let { listCache } = this;
      this.list = JSON.parse(JSON.stringify(listCache));
    },
    expendAll() {
      this.init();
    },
  },
};
</script>

<style scoped lang="less">
.warp {
  padding: 10px;
}
.mind {
  padding: 20px;
  // height: calc(100vh - 150px);
  // width: calc(100vw - 60px);
  position: fixed;
  user-select: none;
  overflow: auto;
  background-color: #fff;
}
.mindRank {
  cursor: move;
}
.header {
  display: inline-block;
  align-items: center;
  position: fixed;
  z-index: 2;
  background-color: #fff;
  & > div {
    display: inline-block;
    margin-right: 20px;
  }
}
</style>

src/views/mind/components/index.js

export function expendfn({
  list = [],
  id = '',
  isExpend = false // 默认展开/关闭
}) {
  if (list.length === 0) return [];
  let arr = JSON.parse(JSON.stringify(list));
  id === '' && !isExpend && defaultfn(arr, id, isExpend); // 刷新
  
  // 刷新
  function defaultfn(lists) {
    lists.forEach((x) => {
      x.isExpandBefore = false;
      x.isExpandAfter = false
      if (x.children) defaultfn(x.children);
    });
  }
  return arr;
}

src/router/index.js

import Vue from 'vue'
Vue.use(VueRouter)
const routes = [{
  path: '/mind',
  naem: 'mind',
  component: () => import('@/views/mind/mind.vue')
}, ]

const router = new VueRouter({
  routes
})

export default router

src/main.js

import '@/directive/index.js'
import 'element-ui/lib/theme-chalk/index.css';

import App from './App.vue'
import ElementUI from 'element-ui';
import Vue from 'vue'
import jm from 'vue-jsmind'
import router from './router'
import store from './store'

Vue.config.productionTip = false
Vue.use(ElementUI);

Vue.use(jm)
if (window.jsMind) {
  console.log('wind')
  Vue.prototype.jsMind = window.jsMind
}
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android开发JetPack-Databinding组件

DataBinding介绍Android开发JetPack-Databinding组件1. 什么是databinding&#xff08;1&#xff09;简介&#xff08;2&#xff09;意义2. databinding基本使用&#xff08;1&#xff09;启用databinding&#xff08;2&#xff09;定义一个布局&#xff08;3&#xff09;创建一…

Redis02:企业架构介绍以及redis介绍

企业架构介绍以及redis介绍NoSql概述单机Mysql的演进当今企业架构分析NoSql概述 单机Mysql的演进 1、单机Mysql的年代 90年代一个基本的网站访问量一般不会太大&#xff0c;单个数据库完全足够&#xff01;那个时候更多的去使用静态html&#xff0c;服务器没有太大的压力。这…

HCIP实验3-1:IBGP与EBGP

实验 3-1 IBGP与EBGP 学习目的 掌握区域内部BGP的配置方法掌握多区域BGP的配置方法观察BGP的邻居表和数据库掌握BGP更新源的配置方法掌握EBGP多跳的配置方法观察IBGP和EBGP中路由的下一跳的变化掌握IBGP中下一跳的配置掌握BGP的Network命令的配置方法 拓扑图 场景 你是公司…

open label file.(This can be normal only if you use MScoco)

E:\yolov4-rubish\darknet\our_data\ImagesAug 把标签的txt文件和Jpg文件放在同一个文件夹

Kafka 入门知识,看这一篇就够了(上)

目录01 初识 Kafka02 topic & partition03 Kafka 分布式最近在学习 Kafka&#xff08;别问&#xff0c;问就是公司在用 &#xff09;&#xff0c;将学习过程中的笔记整理出来分享给大家&#xff0c;就当是入入门 01 初识 Kafka Kafka 最早是由 LinkedIn 公司开发的&#x…

python中numpy数组形状和计算

1. numpy数组形状 数组可以理解为是矩阵&#xff0c;所以会涉及几行几列 import numpy as np import randomt1 np.array([[1,2,3],[4,5,6]]) print(t1) print(t1.shape)在这段逻辑里&#xff0c;t1是个数组&#xff0c;输出结果后 (2, 3)表示的就是这个数组是一个2行3列的矩…

计算机系统基础实验——数据的机器级表示(计算浮点数 f 的绝对值[f])

题目要求&#xff1a; 这个函数计算浮点数f的绝对值[f]。如果f是NaN&#xff0c;函数应该简单的返回f。 Unsigned float_abs (unsiged f) { /**************/ return/*******/; } 先分析题目&#xff0c;题目有两个要求&#xff1a; 1.判断f是否是NAN类型&#xff0c;如果是返…

文件管理,给文件名称插入纯数字详细步骤

在日常办公过程中&#xff0c;我们经常会需要对文件名称进行重命名&#xff0c;如何给文件插入纯数字呢&#xff1f;一两个文件还好可以自定义重命名&#xff0c;面对大量文件的时候&#xff0c;怎么在文件名称中插入纯数字呢&#xff1f;一起来看看吧&#xff01; 第一步&…

这 5 本数据分析书籍,都是经典中的经典

下面要推荐的 5 本数据分析书籍对于数据分析领域而言&#xff0c;经典、经典、还是经典。 强烈建议先收藏&#xff0c;再观看。 《深入浅出数据分析》 数据分析入门第一本。 本书构思跌宕起伏&#xff0c;行文妙趣横生&#xff0c;无论是职场老手&#xff0c;还是业界新人&…

S2SH小区物业管理理系统计算机毕业论文Java项目源码下载

&#x1f496;&#x1f496;更多项目资源&#xff0c;最下方联系我们✨✨✨✨✨✨ 目录 Java项目介绍 资料获取 Java项目介绍 计算机毕业设计java毕设之S2SH小区物业系统_哔哩哔哩_bilibili计算机毕业设计java毕设之S2SH小区物业系统共计2条视频&#xff0c;包括&#xff1…

香农-范诺编码(Shannon–Fano Coding)

香农-范诺编码香农-范诺编码简介算法示例香农-范诺编码 简介 香农-范诺编码&#xff08;Shannon–Fano Coding&#xff09;是一种基于一组符号集及其出现的或然率&#xff08;估量或测量所得&#xff09;&#xff0c;从而构建前缀码的技术。 一般过程&#xff1a;符号从最大可…

太强了!GitHub上白嫖的SpringCloud微服务进阶宝典,啃完感觉能吊锤面试官!

自 2014 年起&#xff0c;微服务技术一直火热至今。随着越来越完善的微服务技术栈的发布&#xff0c;以及越来越多的微服务项目实际的落地和上线&#xff0c;使用 Java 技术栈的企业应该都在尝试或者已经落地了各自的微服务项目。同时&#xff0c;通过招聘网站的信息和每次面试…

正确理解线程WAITING状态

正确理解线程WAITING状态 今天来学习下&#xff0c;Java的线程状态&#xff0c;重点讨论下thread.state.WAITING。讨论下线程如何进入此状态&#xff0c;以及它们之间的区别。最后&#xff0c;我们进一步了解java.util.concurrent.locks.LockSupport&#xff0c;它提供了几种用…

【分布式能源的选址与定容】基于非支配排序多目标遗传优化算法求解分布式能源的选址与定容(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

AOP通知获取数据

之前说到了AOP可以对原始方法进行增强&#xff0c;那么AOP是否可以获取到原始方法的数据并对原始方法的数据利用增强方法进行处理呢&#xff1f;我们将从获取参数、获取返回值和获取异常三个方面来研究。 首先&#xff0c;我们可以知道&#xff0c;所有的通知类型都可以获取参数…

微服务框架 SpringCloud微服务架构 10 使用Docker 10.6 容器命令练习

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构10 使用Docker10.6 容器命令练习10.6.1 直接开干10 使用Docker 10.6 容器…

阿里“重推”的Spring+Boot+MVC+CloudAlibaba学习手册,开源下载

Spring框架自诞生以来一直备受开发者青睐&#xff0c;有人亲切的称之为&#xff1a;Spring 全家桶。Spring更是避免了重复造轮子的工作并跟随着互联网行业的发展做出不断的更新&#xff0c;很多研发人员把spring看作心目中最好的Java项目&#xff0c;没有之一。 可以毫不夸张的…

VS Code 扩展开发如何保持用户视觉体验一致

本文介绍如何在 VS Code 插件的 webview 中加载本地的资源文件&#xff0c;并如何使用 VS Code 自身的 UI 来实现用户视觉体验的一致。 背景 最近想做一个 VS Code 的插件用来简便我使用 VS Code 来编辑 Markdown 博客的体验&#xff0c;在设计插件的过程中&#xff0c;因为需…

计算机毕业论文Java项目源码下载S2SH智慧社区管理系统[包运行成功]

&#x1f496;&#x1f496;更多项目资源&#xff0c;最下方联系我们✨✨✨✨✨✨ 目录 Java项目介绍 资料获取 Java项目介绍 《基于S2SH实现的智慧社区管理系统》该项目采用技术jsp、strust2、Spring、hibernate、tomcat服务器、mysql数据库 &#xff0c;项目含有源码、答…

力扣hot100——第6天:32最长有效括号、33搜索旋转排序数组、34在排序数组中查找元素的第一个和最后一个位置

文章目录1.32最长有效括号1.1.题目1.2.解答2.33搜索旋转排序数组2.1.题目2.2.解答3.34在排序数组中查找元素的第一个和最后一个位置【代码随想录已刷】1.32最长有效括号 参考&#xff1a;力扣题目链接&#xff1b;题解1&#xff0c;题解2 1.1.题目 1.2.解答 这道题目官方的题…