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


    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组件里面再次引入即可递归
    多余线段去除: 刚开始的做法是直接设置子集的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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215

    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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 相关阅读:
    应用案例|基于高精度三维机器视觉引导机器人自动分拣包裹的应用
    openlayers示例教程100+【目录】
    一文了解微分段应用场景与实现机制
    第二届“移动云杯”大赛行业赛道(行业应用创新子赛道)赛题密码,请速速转发!...
    GUI编程入门
    Linux Day14 :线程的创建与同步
    基于FPGA的正弦PWM产生系统verilog实现
    Solidity - 算术运算的截断模式(unchecked)与检查模式(checked)- 0.8.0新特性
    java中的IO流之转换流(编码转换)
    MySQL的子查询
  • 原文地址:https://blog.csdn.net/wuj1935/article/details/128143464