在一些組件庫中,狀態管理並非用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--遞歸組件
感謝觀看!