之前想着做一个树图,通过v-for套3层,实现了一个三层树图

vue如何根据盆栽生成模型 vue实现树状图_vue如何根据盆栽生成模型

 后来突然开窍了,vue组件的形式,可以组件套组件,方便多了

仿windows文件管理器的文件树

先上图

vue如何根据盆栽生成模型 vue实现树状图_javascript_02

定义数据结构

[
        {
                "path":"/快速访问",
                "name":"快速访问",
                "icon":"quick_access",
                "type":"dir",
                "child":[]
        },
]

通过child无线嵌套。

基础结构

vue如何根据盆栽生成模型 vue实现树状图_javascript_03

当前节点中包含,展开图标,合并图标,空白图标,文件夹图标,文件名。

子节点就是遍历当前节点的child数组生成。

需要注意两个问题

(1)树图不是一下子就把全部叶子节点都给展开的,未展开前,需不需要提前渲染好。

(2)叶子节点多次展开合并,子节点不应该每次都重复生成渲染。

这里使用两个变量来控制。

isExtend: false, 是否展开节点,通过v-show开控制
isCreatedChild: false,是否生成节点,通过v-if来控制

当第一次展开的时候,生成一次子节点。isCreatedChild变为true,后续不在变动。

后续展开合并通过控制isExtend即可。

这样也是实现了异步加载树节点。

无子节点的情况

添加如下变量,默认为true

isExistChild: true,

在第一次展开的时候判断一次,没有子节点时置为false,后续不在改动

当isExistChild为false时,将展开合并图标隐藏,不显示。

设置选中样式

如何实现只选中一个节点?

设置选中和未选中的样式

.file_tree_node_div {
  white-space: nowrap;
  border: 1px solid transparent;

}

.file_tree_node_div:hover {
  background-color: #e9f3f3;
  border: 1px solid #e9f3f3;
}
.file_tree_node_div_selected {
  white-space: nowrap;
  background-color: #cce6ee;
  border: 1px solid #cce6ee;
}
.file_tree_node_div_selected:hover {
  border: 1px solid #b3dce7;
}

节点个数不固定,隔代节点之间通信困难,当点击一个节点标记为选中,遍历其他节点,将选中的置为不选中,这种消耗太大。

不如直接操作document来的快,

setSelected(){
      let arr=document.getElementsByClassName("file_tree_node_div_selected");
      if(arr&&arr.length>0)arr[0].className='file_tree_node_div';
      this.$refs.node.className='file_tree_node_div_selected';
    },

完整效果

vue如何根据盆栽生成模型 vue实现树状图_ecmascript_04

附:节点源码

<template>
  <div>
    <div ref="node" class="file_tree_node_div" >
      <b-icon v-if="isExistChild&&!isExtend" @click="handle_extend_node()" class="file_tree_node_arrow"
              local="arrow_thick_right" style="color: #adadad;"></b-icon>
      <b-icon v-else-if="isExistChild&&isExtend" @click="handle_extend_node()" class="file_tree_node_arrow"
              local="arrow_thick_bottom" style="color: #535353;"></b-icon>
      <img v-else class="file_tree_node_arrow" src="" style="opacity: 0;">
      <img class="file_tree_node_icon" :src="icon">
      <button class="empty_button file_tree_node_name" @click="handle_change_path">{{ name }}</button>
    </div>
    <div v-if="isCreatedChild" v-show="isExtend">
      <template v-for="(item,index) in child" :key="index+'b'">
        <FileTreeNode v-if="item.type==='dir'" :data="item" style="margin-left: 10px;"></FileTreeNode>
      </template>
    </div>
  </div>
</template>

<script>
export default {
  name: "FileTreeNode",
  props: {
    data: Object,
  },
  data() {
    return {
      name: '',
      icon: '',
      isExistChild: true,
      isExtend: false,
      isCreatedChild: false,
      isSelected:false,
      child: [],
    }
  },
  created() {
    this.init(this.data);
  },
  methods: {
    init(data) {
      if (data) {
        this.name = data.name;
        if (!data.icon || data.icon === '') this.icon = require("@/assets/file/dir.png")
        this.icon = require("@/assets/file/" + data.icon+'.png');
        this.child = data.child;
        this.path = data.path;
        if(data.isExtend)this.handle_extend_node();
      }
    },
    /**
     * 展开节点或者关闭节点
     */
    handle_extend_node() {
      this.isExistChild = this.checkExistDir(this.child);
      this.isExtend = !this.isExtend;
      if (!this.isCreatedChild) {
        this.isCreatedChild = true;
      }
    },
    /**
     * 检查路径下是否存在文件夹
     */
    checkExistDir(child) {
      if (child) {
        for (let i = 0; i < child.length; i++) {
          if (child[i].type === 'dir') return true;
        }
      }
      return false;
    },
    /**
     * 获取当前节点路径,每一个节点都将数组传递给父节点,等父节点添加好文件夹名称后,子节点在添加
     */
    getPath(arr){
      this.$parent.getPath(arr);
      if(!this.$parent.isRoot){
        arr.push(this.name);
      }
      return arr;
    },

    /**
     * 点击树图节点
     */
    handle_change_path(){
      if(this.$refs.node.className!=='file_tree_node_div_selected')
      this.setSelected();
      let path=[];
      if('此电脑 快速访问'.indexOf(this.name)>-1){
        path.push(this.name);
      }else{
        path=this.getPath([]);
      }
      this.$event.$emit('FileManager_set_data',path);
    },
    setSelected(){
      let arr=document.getElementsByClassName("file_tree_node_div_selected");
      if(arr&&arr.length>0)arr[0].className='file_tree_node_div';
      this.$refs.node.className='file_tree_node_div_selected';
    },

  },
}
</script>

<style>
.file_tree_node_arrow {
  width: 25px;
  height: 25px;
  opacity: 0;
  transition: all 2s ease-out;
}

.file_tree_node_icon {
  width: 25px;
  height: 25px;
  margin-top: -4px;
  padding: 2px;
}

.file_tree_node_div {
  white-space: nowrap;
  border: 1px solid transparent;

}

.file_tree_node_div:hover {
  background-color: #e9f3f3;
  border: 1px solid #e9f3f3;
}
.file_tree_node_div_selected {
  white-space: nowrap;
  background-color: #cce6ee;
  border: 1px solid #cce6ee;
}
.file_tree_node_div_selected:hover {
  border: 1px solid #b3dce7;
}
.file_tree_node_name {
  padding-top: 2px;
  padding-left: 3px;
  cursor: pointer;
}

</style>