Vue遞迴元件+Vuex開發樹形元件Tree--資料模組

fengyangyang發表於2019-03-29

Vue遞迴元件+Vuex開發樹形元件Tree--資料模組

在一些元件庫中,狀態管理並不是用vuex實現的,因為是元件,考慮到環境不可能指定儲存庫來進行儲存,元件庫基本是維護了自己的一個狀態庫,element-ui tree元件也是建立了自己狀態管理元件,很簡單就是一個store.js 檔案,裡面存放各種資料。本文來源於一次專案的功能開發,寫文章旨在傳遞Tree元件的編寫思想,因此選用了Vuex作為全域性的狀態管理。

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

擼程式碼:

store資料夾下新建index.js作為全域性資料管理:

//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的程式碼。

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

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

倉庫寫好了,那麼現在就開始互動的部分。

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

開發前先寫一個工具庫,集中一些axios請求和工具函式。首先,想一下互動的思路,已知後臺會返回每一節點的唯一id,點選這個節點的時候根據id向後臺傳送請求獲取當前id下一層資料,當得到一個節點的陣列的時候如何插入store中的data倉庫?換句話說插入到data中的哪一層?刪除也是一樣,得到點選id了,那麼刪除data的哪一層節點?因為是資料驅動檢視,所以我們只增刪改data倉庫,那麼Dom就會觸發相應的更新。因此需要一些遞迴函式來輔助操作。

遞迴新增與刪除公共方法:
//新建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[]中去查詢,還不存在則遞迴查詢。上面兩個函式封裝了後臺獲取資料增加與刪除的函式,還需要封裝自定義新增的函式,可以自定義前端新增資料,而不是從後臺獲取的資料,原理相同,只是增加一個資料模板。

//根據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給後臺,如果後臺返回成功那麼就執行自定義新增或者刪除的程式碼就是了。現在是這樣的效果:

Vue遞迴元件+Vuex開發樹形元件Tree--資料模組

一個基本的樹狀選單就開發完成了,“改” 這個操作也很簡單,說一下就不寫了,點選修改將當前標題標籤切換成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--遞迴元件

感謝觀看!

相關文章