13. 目录树组件实战

一、前言

开源UI库对于常用的前端控件已经实现的非常好了,比如前面一章介绍的Element-Plus开源库的使用。

但对于一些特殊的、不那么通用的组件,一般开源库都不会提供相应的实现,此时就需要我们自己封装了。

比如文本要介绍的“目录树组件”,在开源库中就很少有实现的,而其在一些需要显示文件目录的地方又确实很有用。

所以本文就带大家来看看一个目录树的实现逻辑。

二、思路

写代码前思路很重要,目录树大家应该都很熟悉,比如windows系统上的文件资源管理器中,就有很多目录,只不过它的目录结构并不允许多个层次的文件同时出现。

所以这里我以vscode中目录树为基准:

image-20240125192536167

它就可以很好的将不同目录下的文件全部展示到一个控件中去。

而我们的目的就是用vue实现出这样一个目录结构,经过前面的我的摸索,大概可以将目录树分为两个结构:

  • DirNode:目录节点。

目录树最外层肯定是一个目录,也就是这里的DirNode,每个目录里面只有两个东西:目录、文件,其中文件可以直接展示,而子目录则继续使用DirNode表示,这样就完成了无限递归嵌套的任务,无论你有多少层,都可以用DirNode表示出来。

  • FileTree:目录树

这个结构主要是用来整理上面的DirNode样式的,比如我们一般需要设置目录的最大高度,如果超出了这个高度就需要显示滑动条。

三、代码实现

有了思路,我们就可以开始来写代码了,根据前面的思路,这里就将其分开作为两个组件进行实现。

1.DirNode

首先是我们的重头戏DirNode组件,它代表了一个目录结构,并且包含直接子文件、子目录。

首先是html结构:

image-20231204062246588

主要分为两部分:

  1. title:用于显示当前目录的名字、图标,并监视其点击事件,用于展开、关闭目录
  2. children:该目录的所有直接子文件、子目录,通过变量is_open来确定当前是否需要展示

首先是title部分:

image-20240125191646254

里面主要的内容其实就是显示一个目录名字,并且这个目录名字是通过父组件的prop传递进来的。

至于ArrowDownArrowRight是我用vue封装的两张svg图片,代表下箭头、右箭头,通过is_open变量、v-if语句来确定当前要展示哪一张图片,最终效果和vscode中一样:

image-20240125191726354

然后是children结构,其内部再一次分为两个部分:

image-20240125191833339

上面的部分是通过遍历目录数据来显示目录的,其内部就是用的本组件:DirNode

至于它身上传递的这些prop值,我们后面再提,现在只需要知道这里是在循环渲染当前目录下的所有子目录就行了。

然后下面的部分就是在循环渲染当前目录下的所有文件,其内部直接显示文件名。

实际使用过程中可能还需要通过不同文件的后缀名在这里显示不同文件的图标。

同时我还给每一个文件标签添加了一个双击事件dblclick,这是用于双击打开文件的操作的。

上面的是基本结构,下面我们再来看看ts代码:

image-20240125192107916

这里最重要的是这个prop

  1. name:当前DirNode要显示的目录名字
  2. path:当前DirNode要显示的目录完整路径
  3. is_openDirNode是否在渲染成功的同时默认将其它的所有子目录打开。
  4. fun_open_file:用于打开文件的函数,由外界提供,DirNode中只负责在文件被双击的时候调用这个函数。
  5. fun_open_dir:用于打开目录,当DirNode被点击后,如果要展开其子目录,就需要通过调用这个函数来获取它所有的子目录、子文件数据。

然后是is_open这个ref响应式变量,前面template中大量用到,是用来标志当前是否为展开目录的状态。

然后是下面的children,通过reactive来实现响应式,初始化为一个对象中包含两个数组、且都为空,它的类型为:FileChildren

image-20231204064859799

js中应该不能写类型,这是ts提供的功能,实际上这就是两个数组,dir中存放所有目录的名字与路径,file中存放所有文件的名字与路径。

前面template中的children类标签中,分配遍历渲染文件夹、文件,就是用的这个响应式变量。

初始化函数onMounted就简单了,主要是用来判断是否要在初始化时打开文件夹:

image-20231204065252208

这里是通过调用另一个专门控制目录点击函数fun_dir实现的:

image-20231204065345110

这个函数中通过判断is_open的值,如果为打开状态,那就将其关闭,否则,那就调用外面传入的fun_open_dir函数,传入当前的路径,得到其子目录、文件数据,将其显示到页面上。

至于打开文件的函数:

image-20231204065551462

就是直接调用了一下外面传入的打开文件函数而已,参数为文件路径。

至于最后的样式,就不多解释了,注意我样式用的是less语法,你需要在项目中安装less加载器后才能使用。

并且唯一需要注意的点是这个外边距:

image-20231204065824523

通过设置左外边距,就能让其子文件与目录名错开,从而看上去更有层次感。

最后总结一下它的运行逻辑:

  1. 父组件通过传入name为当前目录DirNode的名字、path为当前的路径,is_open为默认是否要打开,fun_open_file为当用户双击某个文件时,要调用的函数,fun_open_dir为当目录被点击后,如果需要展开,则通过调用这个函数获取到目录中的数据。
  2. 一般DirNode默认是关闭状态,所以一旦点击DirNode标签就会触发fun_dir函数,该函数内部调用fun_open_dir函数获取到该目录内的所有子目录、子文件,将其设置在children身上,然后前面html代码中的children类标签中就会根据这个数据将其渲染出来。
  3. 如果双击了某个文件,那就会调用fun_openfile函数,这个函数内部实际上是在调用外面传入的fun_open_file函数。

完整代码如下,仅供参考,你需要自行配置好tsless环境:

<template>
    <div class="DirNode">
        <div class="title" @click="fun_dir">
            <ArrowDown w='18' h="18" v-if="is_open"></ArrowDown>
            <ArrowRight w='18' h="18" v-else></ArrowRight>
            <div>{{ prop.name }}</div>
        </div>
        <div class="children" v-if="is_open">
            <div v-for="item in children.dir" class="dir">
                <DirNode :is_open="false" :name="item.name" :path="item.path" :fun_open_file="prop.fun_open_file"
                    :fun_open_dir="prop.fun_open_dir">
                </DirNode>
            </div>
            <div v-for="item in children.file" class="file" @dblclick="fun_openfile(item.path)">
                <span class="name">{{ item.name }}</span>
            </div>
        </div>
    </div>
</template>

作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux