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組件里面再次引入<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' : '',]"><iv-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><iv-if="item.children && !item.isExpandBefore"class="iconremove":class="[!item.isExpandAfter? 'el-icon-remove-outline': 'el-icon-circle-plus-outline',]"@click="expendAfter(item)"></i><divv-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-numberv-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 = falseif (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 routersrc/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')總結
以上是生活随笔為你收集整理的vue纯手写思维导图,拒绝插件(cv即用)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 易基因单细胞转录组测序(smart-se
- 下一篇: kafka 单机配置外网无法访问