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给后台,如果后台返回成功那么就执行自定义添加或者删除的代码就是了。现在是这样的效果:

一个基本的树状菜单就开发完成了,“改” 这个操作也很简单,说一下就不写了,点击修改将当前标题标签切换成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--递归组件

感谢观看!

转载于:https://juejin.im/post/5c9dc42551882531cf31ae21

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值