在一些元件庫中,狀態管理並不是用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給後臺,如果後臺返回成功那麼就執行自定義新增或者刪除的程式碼就是了。現在是這樣的效果:
一個基本的樹狀選單就開發完成了,“改” 這個操作也很簡單,說一下就不寫了,點選修改將當前標題標籤切換成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--遞迴元件
感謝觀看!