一、前言
开源UI库对于常用的前端控件已经实现的非常好了,比如前面一章介绍的Element-Plus开源库的使用。
但对于一些特殊的、不那么通用的组件,一般开源库都不会提供相应的实现,此时就需要我们自己封装了。
比如文本要介绍的“目录树组件”,在开源库中就很少有实现的,而其在一些需要显示文件目录的地方又确实很有用。
所以本文就带大家来看看一个目录树的实现逻辑。
二、思路
写代码前思路很重要,目录树大家应该都很熟悉,比如windows系统上的文件资源管理器中,就有很多目录,只不过它的目录结构并不允许多个层次的文件同时出现。
所以这里我以vscode
中目录树为基准:
它就可以很好的将不同目录下的文件全部展示到一个控件中去。
而我们的目的就是用vue实现出这样一个目录结构,经过前面的我的摸索,大概可以将目录树分为两个结构:
DirNode
:目录节点。
目录树最外层肯定是一个目录,也就是这里的DirNode
,每个目录里面只有两个东西:目录、文件,其中文件可以直接展示,而子目录则继续使用DirNode
表示,这样就完成了无限递归嵌套的任务,无论你有多少层,都可以用DirNode
表示出来。
FileTree
:目录树
这个结构主要是用来整理上面的DirNode
样式的,比如我们一般需要设置目录的最大高度,如果超出了这个高度就需要显示滑动条。
三、代码实现
有了思路,我们就可以开始来写代码了,根据前面的思路,这里就将其分开作为两个组件进行实现。
1.DirNode
首先是我们的重头戏DirNode
组件,它代表了一个目录结构,并且包含直接子文件、子目录。
首先是html
结构:
主要分为两部分:
title
:用于显示当前目录的名字、图标,并监视其点击事件,用于展开、关闭目录children
:该目录的所有直接子文件、子目录,通过变量is_open
来确定当前是否需要展示
首先是title
部分:
里面主要的内容其实就是显示一个目录名字,并且这个目录名字是通过父组件的prop
传递进来的。
至于ArrowDown
与ArrowRight
是我用vue封装的两张svg
图片,代表下箭头、右箭头,通过is_open
变量、v-if
语句来确定当前要展示哪一张图片,最终效果和vscode中一样:
然后是children
结构,其内部再一次分为两个部分:
上面的部分是通过遍历目录数据来显示目录的,其内部就是用的本组件:DirNode
。
至于它身上传递的这些prop
值,我们后面再提,现在只需要知道这里是在循环渲染当前目录下的所有子目录就行了。
然后下面的部分就是在循环渲染当前目录下的所有文件,其内部直接显示文件名。
实际使用过程中可能还需要通过不同文件的后缀名在这里显示不同文件的图标。
同时我还给每一个文件标签添加了一个双击事件dblclick
,这是用于双击打开文件的操作的。
上面的是基本结构,下面我们再来看看ts代码:
这里最重要的是这个prop
:
name
:当前DirNode
要显示的目录名字path
:当前DirNode
要显示的目录完整路径is_open
:DirNode
是否在渲染成功的同时默认将其它的所有子目录打开。fun_open_file
:用于打开文件的函数,由外界提供,DirNode
中只负责在文件被双击的时候调用这个函数。fun_open_dir
:用于打开目录,当DirNode
被点击后,如果要展开其子目录,就需要通过调用这个函数来获取它所有的子目录、子文件数据。
然后是is_open
这个ref
响应式变量,前面template
中大量用到,是用来标志当前是否为展开目录的状态。
然后是下面的children
,通过reactive
来实现响应式,初始化为一个对象中包含两个数组、且都为空,它的类型为:FileChildren
js
中应该不能写类型,这是ts
提供的功能,实际上这就是两个数组,dir中存放所有目录的名字与路径,file
中存放所有文件的名字与路径。
前面template
中的children
类标签中,分配遍历渲染文件夹、文件,就是用的这个响应式变量。
初始化函数onMounted
就简单了,主要是用来判断是否要在初始化时打开文件夹:
这里是通过调用另一个专门控制目录点击函数fun_dir
实现的:
这个函数中通过判断is_open
的值,如果为打开状态,那就将其关闭,否则,那就调用外面传入的fun_open_dir
函数,传入当前的路径,得到其子目录、文件数据,将其显示到页面上。
至于打开文件的函数:
就是直接调用了一下外面传入的打开文件函数而已,参数为文件路径。
至于最后的样式,就不多解释了,注意我样式用的是less
语法,你需要在项目中安装less
加载器后才能使用。
并且唯一需要注意的点是这个外边距:
通过设置左外边距,就能让其子文件与目录名错开,从而看上去更有层次感。
最后总结一下它的运行逻辑:
- 父组件通过传入
name
为当前目录DirNode
的名字、path
为当前的路径,is_open
为默认是否要打开,fun_open_file
为当用户双击某个文件时,要调用的函数,fun_open_dir
为当目录被点击后,如果需要展开,则通过调用这个函数获取到目录中的数据。 - 一般
DirNode
默认是关闭状态,所以一旦点击DirNode
标签就会触发fun_dir
函数,该函数内部调用fun_open_dir
函数获取到该目录内的所有子目录、子文件,将其设置在children
身上,然后前面html
代码中的children
类标签中就会根据这个数据将其渲染出来。 - 如果双击了某个文件,那就会调用
fun_openfile
函数,这个函数内部实际上是在调用外面传入的fun_open_file
函数。
完整代码如下,仅供参考,你需要自行配置好ts
、less
环境:
<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>