Vue遞歸組件+Vuex開發樹形組件Tree--數據模塊

在一些組件庫中,狀態管理並非用vuex實現的,由於是組件,考慮到環境不可能指定存儲庫來進行存儲,組件庫基本是維護了本身的一個狀態庫,element-ui tree組件也是建立了本身狀態管理組件,很簡單就是一個store.js 文件,裏面存放各類數據。本文來源於一次項目的功能開發,寫文章旨在傳遞Tree組件的編寫思想,所以選用了Vuex做爲全局的狀態管理。javascript

上一篇 Vue遞歸組件+Vuex開發樹形組件Tree--遞歸組件 已經完成了組件方面的建立,這一篇主要編寫邏輯與數據更新存儲。html

擼代碼:前端

store文件夾下新建index.js做爲全局數據管理:vue

//index.js入口文件
import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);
import data_store from './components/data_store.js';
import loading_store from './components/loading_store.js';
export default new vuex.Store({
    modules: {
        data_store: data_store,
        loading_store: loading_store
    }
})
複製代碼

項目中目前維護了兩種狀態一種是Tree數據,一種是公共的loading的狀態,爲了可拓展性,將index.js分離成modules的形式,每新增一個狀態庫只須要增長一條,而不須要頻繁修改index.js的代碼。java

//store/modules/data_store.js
export default {
    state:{
        data:{}
    },
    mutations:{
        set_data(state,data){
            state.data=data
        }
    }
}
複製代碼

很簡單,修改data與提交的操做。node

倉庫寫好了,那麼如今就開始交互的部分。ios

需求是,每點擊一層,那麼就去請求後臺獲取他下一層的數據,有數據則展開下拉。而且每個node節點都有增刪改的功能。好同樣樣來寫:vuex

開發前先寫一個工具庫,集中一些axios請求和工具函數。首先,想一下交互的思路,已知後臺會返回每一節點的惟一id,點擊這個節點的時候根據id向後臺發送請求獲取當前id下一層數據,當獲得一個節點的數組的時候如何插入store中的data倉庫?換句話說插入到data中的哪一層?刪除也是同樣,獲得點擊id了,那麼刪除data的哪一層節點?由於是數據驅動視圖,因此咱們只增刪改data倉庫,那麼Dom就會觸發相應的更新。所以須要一些遞歸函數來輔助操做。element-ui

遞歸添加與刪除公共方法:
//新建utils.js
/* * tree: tree 的數據,存放於vuex中 * data:須要插入的數據節點組成的對象。 */
export function getData(tree, data) { 
  if (tree.id == data.id) {
    tree.nodes = data.nodes;
  } else {
    for (let i in tree.nodes) {
      if (tree.nodes[i].id == data.id) {
        tree.nodes[i].nodes = data.nodes;
        break;
      } else {
        getData(tree.nodes[i], data)
      }
    }
  }
}
//刪除數據 提供id刪除對應的節點
export function deleteData(tree, id) {
  if (tree.id == id) {
    tree.nodes.splice(0, 1);
  } else {
    for (let i in tree.nodes) {
      if (tree.nodes[i].id == id) {
        console.log(typeof tree.nodes[i])
        tree.nodes.splice(i, 1);
        break;
      } else {
        deleteData(tree.nodes[i], id)
      }
    }
  }
}
複製代碼

遞歸:傳入id和對比的對象數組,首先對比根層級,若是id匹配執行相應的增刪,若是不匹配則向下nodes[]中去查找,還不存在則遞歸查找。上面兩個函數封裝了後臺獲取數據增長與刪除的函數,還須要封裝自定義添加的函數,能夠自定義前端添加數據,而不是從後臺獲取的數據,原理相同,只是增長一個數據模板。axios

//根據id添加新數據 template數據模板,能夠根據模態框取值
export function addDataByID(arr, id, template) {
  if (arr.id == id) {
    arr.nodes.push(template)
  } else {
    for (let i in arr.nodes) {
      if (arr.nodes[i].id == id) {
        arr.nodes[i].nodes.push(template);
        break;
      } else {
        addDataByID(arr.nodes[i], id, template)
      }
    }
  }
}

複製代碼

函數比較簡單就是一個遞歸函數,繼續修改代碼 :

//store/modules/data_store.js
import {getData, deleteData, addDataByID} from "common/utils.js";
export default {
    state:{
        data:{},
        node:[]
    },
    mutations:{
        set_data(state,data){
            state.data=data
        },
        //點擊節點展現下一級子節點
        getNodesData(state, data) {
          getData(state.data, data)
        },
        //刪除子節點
        delData(state, id) {
          deleteData(state.data, id)
        },
        //添加子節點
        addData(state, id, dataTemplate) {
            addDataByID(state.data ,id, dataTemplate)
        }
    }
}
複製代碼
// TreeMenu.vue
<template>
 //...
<template>

<script> import { mapState, mapMutations } from "vuex"; //... computed() { ...mapState({ treeData(state) { //vuex中的樹的數據 return state.data_store.data; } }) }, methods: { //...接上文 ...mapMutations(['getNodesData','delData','addData']), //添加loadTreeNode後臺獲取節點的函數 loadTreeNode(id) { //點擊節點調用此函數,須要傳遞節點id apiTreeAddress({ parentId: id })//封裝的接口函數,返回節點列表 .then(res => { const dataCache = { id: id, nodes: [] }; //根據id建立模擬的某一層級節點 for (let node of res.result.list) { let data = { id: node.id, //節點id label: node.name, //節點lable isLoad: false, //自定義flag,下文用到 nodes: [] }; dataCache.nodes.push(data); //處理數據後將節點裝入nodes } /*遞歸對比dataCache.id與store內的各級別id,相同 *則插入對應id的nodes數組中成爲下一層數據*/ this.getNodesData(dataCache);//提交到vuex }) .catch(res => { console.log("請求失敗" + res); }); } } </script>
複製代碼

在上篇文章預先寫好的toggleChildren方法插入以下代碼:

//節點點擊事件
toggleChildren(event) {
  this.showChildren = !this.showChildren;
  let id = event.currentTarget.getAttribute("id");
  this.loadTreeNode(id);
},
複製代碼

toggleChildren點擊節點觸發事件,獲取到當前節點的id,而後調用loadTreeNode方法,此方法根據id向後臺查詢到當前id下的子節點,而後數據轉化從新到新的數組,最後提交到vuex 調用其中的遞歸比對的函數,進行數據插入。

這樣就爲每一層node節點綁定了點擊事件,點擊獲取數據顯示。可是這只是點擊事件,那麼第一次加載頁面的時候是沒有根數據的啊,因此要在Tree.vue中寫一個初始化的函數,初始加載根節點:

//Tree.vue

import { mapState, mapMutations } from "vuex";
...

methods: {
  ...mapMutations(['set_data']),
  loadRootNode(id) {
    apiTreeAddress({parentId:id}).then(res=>{
     let list = res.result.list;
        let data = {
            id: list[0].id,
            lable: list[0].name,
            isLoad: false,
            nodes:[]
         },
         this.set_data(data);
    })
  },
},
mounted() {
    this.loadRootNode(0);
    //後臺約定,加載頁面經過id = 0;取下一級節點,根據實際狀況有所不一樣
}

複製代碼

這樣一個樹狀菜單點擊加載就作好了:

樹菜單示意圖

自定義添加與刪除
//TreeMenu.vue
<template>
  <div class="tree-menu">
    <div :style="indent" @click="toggleChildren">{{label}}</div>
    <div v-if="showChildren">
      <tree-menu v-for="(item, index) of nodes" :key="index" :nodes="node.nodes" :label="node.label" :depth="depth + 1" ></tree-menu>
    </div>
    <span class="edit-menu">
     <i class="el-icon-plus" @click="add($event)" :id="id"></i> //添加按鈕
     <i class="el-icon-delete" @click="dele($event)" :id="id"></i>  //刪除按鈕
    </span>
  </div>
</template>

 <script> data() { return { // ... count: 1000 } } ... add(e) { let id = e.currentTarget.getAttribute("id"); let dataTemplate = { id: this.count++, label: "xxx人民政府", nodes: [] }; this.addData(id, dataTemplate)//提交到vuex }, dele(e) { let id = e.currentTarget.getAttribute("id"); this.delData(id); //提交到vuex }, </script>
複製代碼

dataTemplate是一個添加的數據模板,可自定義添加,this.count是定義的一個變量,保證每次id都不一樣。實際上添加和刪除是要走後臺的,上面寫的這種是前端頁面展現上的添加與刪除,根據需求決定,可是思路和上面後臺獲取添加是同樣的,點擊傳遞id給後臺,若是後臺返回成功那麼就執行自定義添加或者刪除的代碼就是了。如今是這樣的效果:

一個基本的樹狀菜單就開發完成了,「改」 這個操做也很簡單,說一下就不寫了,點擊修改將當前標題標籤切換成input標籤,輸入完成再賦值給當前元素便可,也是要向後臺傳遞的。還有一些能夠優化的地方,如今每次點擊都會發送請求,須要優化一下: 上文中後臺返回數據後,有一層轉化:

apiTreeAddress({parentId:id}).then(res=>{
   let list = res.result.list;
      let data = {
          id: list[0].id,
          lable: list[0].name,
          isLoad: false, // 子節點是否加載標記
          nodes:[]
       },
       this.set_data(data);
  })
複製代碼

默認當前節點的isLoad字段爲false,意義就是當前節點的子節點未加載:

utils添加函數:

//utils.js 
//獲取節點是否load
export function getLoadState(arr, id) {
 if (arr.id == id) {
   return arr.isLoad
 } else {
   for (let i in arr.nodes) {
     if (arr.nodes[i].id == id) {
       return arr.nodes[i].isLoad
       break;
     } else {
       getLoadState(arr.nodes[i], id)
     }
   }
 }
}
//設置節點Load
export function setLoadState(arr, id) {
 if (arr.id == id) {
   arr.isLoad = true;
 } else {
   for (let i in arr.nodes) {
     if (arr.nodes[i].id == id) {
       arr.nodes[i].isLoad = true;
       break;
     } else {
       setLoadState(arr.nodes[i], id)
     }
   }
 }
}
複製代碼
// store/modules/data_store.js
 ...
//添加設置節點狀態的mutations
mutations:{
 ...
 //設置節點狀態
 setDataLoad(state, id) {
   addDataByID(state.data ,id)
 }
}
複製代碼

而後在每次點擊的時候判斷當前id的isLoad是否爲true,爲真則就不取後臺取子節點,簡單的經過顯示隱藏展現子節點便可(還記得showChildren字段麼),若是爲false,說明子節點沒有獲取過,也就是第一次點擊當前節點,而後正常請求,請求回來後,設置當前節點isLoad true:

//TreeMenu.vue
//...接上文
...mapMutations(['getNodesData','delData','addData','setDataLoad']),//添加setDataLoad設置節點信息
//添加loadTreeNode後臺獲取節點的函數
loadTreeNode(id) {
     if (getLoadState(treeData, id)) {
       return;
     }
     apiTreeAddress({ parentId: id })
       .then(res => {
         var dataCache = { id: id, nodes: [] };
         for (let node of res.result.list) {
           let data = {
             id: node.id,
             label: node.name,
             isLoad: false,
             nodes: []
           };
           dataCache.nodes.push(data);
         }
         //數據返回成功,就設置已經Load
         this.setDataLoad(id);
         /*遞歸對比dataCache.id與store內的各級別id,相同 *則插入對應id的nodes數組中成爲下一層數據*/
         this.getNodesData(dataCache);//提交到vuex
       })
       .catch(res => {
         console.log("請求失敗" + res);
       });
複製代碼

這樣當第一次點擊節點就會取數據,再次點擊就不會進入到取數據。

如今樹形插件已經開發完成了,須要根據上一篇來一塊兒實現。

連接: Vue遞歸組件+Vuex開發樹形組件Tree--遞歸組件

感謝觀看!

相關文章
相關標籤/搜索