一、实现效果
左侧区域支持选择一个系统中的文件夹,或者将文件夹拖拽到这个区域进行上传,右侧区域可以将文件夹的结构展示为树形结构。
二、代码实现
由于需要使用树形插件zTree,这个插件是依赖于jquery的,所以在项目中我们需要引入:
1、jquery
2、zTree:官网链接API Document [zTree -- jQuery tree plug-ins.]
项目结构很简单,一个zTree源码的文件夹,一个index.html文件
下载完zTree源码之后,解压放到index.html平级的位置
3、在index.html的head标签中引入相关依赖
<script type="text/javascript" src="zTree_v3-master/js/jquery-1.4.4.min.js"></script>
<script type="text/javascript" src="zTree_v3-master/js/jquery.ztree.core.js"></script>
<script type="text/javascript" src="zTree_v3-master/js/jquery.ztree.excheck.js"></script>
<script type="text/javascript" src="zTree_v3-master/js/jquery.ztree.exedit.js"></script>
<link rel="stylesheet" href="zTree_v3-master/css/zTreeStyle/zTreeStyle.css" type="text/css">
4、搭建html结构,左边放置拖拽框和input输入框,右边放置树结构
<style>
li {
list-style: none;
}
a {
cursor: pointer;
}
.icon-download {
font-size: 30px;
cursor: pointer;
}
#drop {
width: 500px;
height: 500px;
border: 1px solid black;
float: left;
}
#folder_container {
float: left;
}
</style>
<!--文件夹下所有文件的信息 -->
<div id="drop">
<input type="file" id="file_input" name="folder" webkitdirectory />
<div style="text-indent: 10px"> 将文件夹拖到这里进行上传</div>
</div>
<!-- 树 -->
<ul id="folder_container">
<ul id="fileTree" class="ztree"></ul>
</ul>
5、input框上传文件夹实现
input框可以增加一个属性webkitdirectory实现上传文件夹,不过这个功能过低版本的浏览器是不兼容的。使用onchange事件监听这个input框的输入事件,就可以在上传文件夹完毕通过event.target.files获取文件夹中的所有文件。
获取的是一个fileList,是一个类数组对象,每一个元素是一个file信息,其中包含文件更新时间、文件名、大小、类型、相对路径。
我们需要解析文件的相对路径,从而获取真正的文件夹结构。
首先需要使用Array.from()方法将类数组对象转换为数组,这样就可以使用数组的迭代方法。
具体的解析过程放在createTree方法中
<script>
let files = []
const fileInput = $('#file_input');
fileInput.bind('change', function (e) {
files = Array.from(e.target.files)
createTree();
})
function createTree() {
}
</script>
我们对文件的相对路径进行解析,最终是要放在zTree树上。zTree的节点之间是通过parentId这个属性来确定层级关系的。如果一个节点的parentId为null,证明这个节点就是根节点;一个节点的parentId等于其父节点的Id。所以在解析相对路径的时候,首先需要获取层数,知道这个文件的结构总共有几层。通过split(‘/’)就可以获取从外到内具体的名字。
(1)在设置根节点之前,要先初始化一棵树
let zNodes = [];
function initNodes() {
zNodes = [];
$.fn.zTree.init($("#fileTree"), setting, zNodes);
}
const setting = {}
function createTree() {
initNodes();
}
(2)确认根节点,也就是根目录的名字。每一个文件的头头都带着根目录的名字。粘贴一下完整的js代码
let files = [];
let zNodes = [];
const treeId = 'fileTree'
const fileInput = $('#file_input');
fileInput.bind('change', function (e) {
files = Array.from(e.target.files)
createTree();
})
function initNodes() {
zNodes = [];
$.fn.zTree.init($("#fileTree"), setting, zNodes);
}
const setting = {}
function createTree() {
initNodes();
const zTree = $.fn.zTree.getZTreeObj(treeId);
let nodes = [];
files.forEach(file => {
nodes = zTree.transformToArray(zTree.getNodes());
const names = file.webkitRelativePath.split('/')
if (nodes.length == 0) {
zTree.addNodes(null, 0, {
id: names[0],
parentId: null,
name: names[0],
filePath: names[0],
})
}
})
}
当前实现效果,有一个根节点了:
(3)处理其他节点。
对names进行循环,相当于从外到内逐层添加节点,先找到父节点,就可以使用addNodes方法添加当前节点。如果是文件(即在最后一层),需要加上filePath记录相对路径的信息。
files.forEach(file => {
nodes = zTree.transformToArray(zTree.getNodes());
const names = file.webkitRelativePath.split('/')
if (nodes.length == 0) {
zTree.addNodes(null, 0, {
id: names[0],
parentId: null,
name: names[0],
filePath: names[0],
})
}
names.forEach((name, index) => {
// index==0时就是name就是根节点
if (index >= 1) {
nodes = zTree.transformToArray(zTree.getNodes());
// 找父节点
const parentId = names[index - 1]
const pNode = nodes.find(node => node.id == parentId)
let newNode = {
id: name,
parentId: parentId,
name: name
}
if (name == names[names.length - 1]) {
newNode.filePath = file.webkitRelativePath
}
zTree.addNodes(pNode, 0, newNode)
}
})
})
实现效果:
6、使用input框上传文件夹并且展示成树形结构的功能已经实现了,下边来做拖拽上传文件夹。
(1)先了解几个拖拽相关API:
拖拽容器相关事件:
(2)在dragenter的时候,可以把容器中的内容显示为“请释放鼠标”,这样会有比较好的体验效果;dragleave的时候,内容显示为“请将文件夹拖拽到此”;drop的时候,要阻止默认事件,否则浏览器会尝试打开文件夹,并且需要恢复原有内容。
const drop = $('#drop');
const originHTML = drop.html();
drop.bind('dragenter', function (e) {
drop.html('请释放鼠标')
})
drop.bind('dragleave', function (e) {
drop.html('请将文件夹拖拽到此')
})
$(document).bind('dragover', function (e) {
e.preventDefault();
return false
})
$(document).bind('drop', function (e) {
e.preventDefault();
drop.html(originHTML)
return false
})
(3)如果是在drop容器中发生drop事件,获取拖拽携带的信息。通过event.originalEvent.dataTransfer.items可以获取到所有的传输对象的信息。在此需要将files清空。
drop.bind('drop', function (e) {
files = [];
const items = e.originalEvent.dataTransfer.items;
for (let i = 0; i < items.length; i++) {
const item = items[i]
console.log(item);
}
})
如果是文件夹的话,item的信息长这样:
(4)对于文件夹使用item.webkitGetAsEntry()
可以查看一下关于这一方法的解释
DataTransferItem.webkitGetAsEntry() - Web API 接口参考 | MDN
drop.bind('drop', function (e) {
files = [];
const items = e.originalEvent.dataTransfer.items;
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.kind == 'file') {
let entry = item.webkitGetAsEntry();
console.log(entry);
}
}
})
打印出来长这样:
可以通过isFile判断是不是文件,通过isDirectory判断是不是文件夹
(5)这里如果不是文件夹,需要提示错误(可以最后再加)
drop.bind('drop', function (e) {
files = [];
const items = e.originalEvent.dataTransfer.items;
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.kind == 'file') {
let entry = item.webkitGetAsEntry();
if (!entry.isDirectory) {
alert('请上传文件夹')
return
}
}
}
})
(6)接下来就需要解析这个文件夹了。解析文件夹需要放到一个递归方法中。
我们可以通过__proto__看一下这个entry的原型是什么:
文件entry的原型:
文件夹entry的原型:
(7)如果是文件的话,可以通过file()方法, 来创建一个拥有当前文件信息的文件
可以查看一下官方解释:FileSystemFileEntry - Web APIs | MDN
function getFilesFromEntry(entry) {
if (entry.isFile) {
entry.file(
file => {
console.log(file);
},
err => {
console.log(err);
}
)
} else {
console.log(entry.__proto__);
}
}
可以看到当前file是没有相对路径的。这个属性是不能手动加上的,所以加一个filePath属性指向相对路径,并且push到files数组中。
function getFilesFromEntry(entry) {
if (entry.isFile) {
entry.file(
file => {
file.filePath = entry.fullPath.slice(1)
files.push(file)
},
err => {
console.log(err);
}
)
} else {
console.log(entry.__proto__);
}
}
(8)如果是文件夹可以使用createReader()方法来解析这个文件夹
FileSystemDirectoryEntry.createReader() - Web APIs | MDN
这个方法会返回一个对象,这个对象可以用来读文件夹中所有的entries
FileSystemDirectoryReader - Web APIs | MDN
readEntries这个方法就可以返回文件夹里的所有entries
function getFilesFromEntry(entry) {
if (entry.isFile) {
entry.file(
file => {
file.filePath = entry.fullPath.slice(1)
files.push(file)
},
err => {
console.log(err);
}
)
} else {
const entryReader = entry.createReader()
entryReader.readEntries(
(results) => {
results.forEach(result => {
getFilesFromEntry(result);
})
},
(error) => {
console.log(error);
}
);
}
}
(9)打印一下files:
当解析完所有文件的时候需要调用createTree方法。怎么判断解析完了所有文件呢?
可能需要先遍历一边所有的entry先算一下count
function getCount(entry) {
if (entry.isFile) {
entry.file(
file => {
count++
},
err => {
console.log(err);
}
)
} else {
const entryReader = entry.createReader()
entryReader.readEntries(
(results) => {
results.forEach(result => {
getCount(result);
})
},
(error) => {
console.log(error);
}
);
}
}
在第二次遍历的时候比较count和files.length如果相等,则调用createTree方法创建文件结构树。
至此拖拽功能实现
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript" src="zTree_v3-master/js/jquery-1.4.4.min.js"></script>
<script type="text/javascript" src="zTree_v3-master/js/jquery.ztree.core.js"></script>
<script type="text/javascript" src="zTree_v3-master/js/jquery.ztree.excheck.js"></script>
<script type="text/javascript" src="zTree_v3-master/js/jquery.ztree.exedit.js"></script>
<link rel="stylesheet" href="zTree_v3-master/css/zTreeStyle/zTreeStyle.css" type="text/css">
<title>Document</title>
<style>
li {
list-style: none;
}
a {
cursor: pointer;
}
.icon-download {
font-size: 30px;
cursor: pointer;
}
#drop {
width: 500px;
height: 500px;
border: 1px solid black;
float: left;
}
#folder_container {
float: left;
}
</style>
</head>
<body>
<!--文件夹下所有文件的信息 -->
<div id="drop">
<input type="file" id="file_input" name="folder" webkitdirectory />
<div style="text-indent: 10px"> 将文件夹拖到这里进行上传</div>
</div>
<!-- 树 -->
<ul id="folder_container">
<ul id="fileTree" class="ztree"></ul>
</ul>
<script>
let files = [];
let zNodes = [];
const treeId = 'fileTree'
const fileInput = $('#file_input');
fileInput.bind('change', function (e) {
files = Array.from(e.target.files)
createTree();
})
function initNodes() {
zNodes = [];
$.fn.zTree.init($("#fileTree"), setting, zNodes);
}
const setting = {}
function createTree() {
console.log(files);
initNodes();
const zTree = $.fn.zTree.getZTreeObj(treeId);
let nodes = [];
files.forEach(file => {
const filePath = file.webkitRelativePath == '' ? file.filePath : file.webkitRelativePath
nodes = zTree.transformToArray(zTree.getNodes());
const names = filePath.split('/')
if (nodes.length == 0) {
zTree.addNodes(null, 0, {
id: names[0],
parentId: null,
name: names[0],
filePath: names[0],
})
}
names.forEach((name, index) => {
// index==0时就是name就是根节点
if (index >= 1) {
nodes = zTree.transformToArray(zTree.getNodes());
// 找父节点
const parentId = names[index - 1]
const pNode = nodes.find(node => node.id == parentId)
let newNode = {
id: name,
parentId: parentId,
name: name
}
if (name == names[names.length - 1]) {
newNode.filePath = filePath
}
zTree.addNodes(pNode, 0, newNode)
}
})
})
}
// 拖拽
const drop = $('#drop');
const originHTML = drop.html();
drop.bind('dragenter', function (e) {
drop.html('请释放鼠标')
})
drop.bind('dragleave', function (e) {
drop.html('请将文件夹拖拽到此')
})
$(document).bind('dragover', function (e) {
e.preventDefault();
return false
})
$(document).bind('drop', function (e) {
e.preventDefault();
drop.html(originHTML)
return false
})
let count = 0
drop.bind('drop', function (e) {
files = [];
const items = e.originalEvent.dataTransfer.items;
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.kind == 'file') {
let entry = item.webkitGetAsEntry();
if (!entry.isDirectory) {
alert('请上传文件夹')
return
}
//递归解析文件夹
getCount(entry)
setTimeout(() => {
getFilesFromEntry(entry)
}, 300)
}
}
})
function getCount(entry) {
if (entry.isFile) {
entry.file(
file => {
count++
},
err => {
console.log(err);
}
)
} else {
const entryReader = entry.createReader()
entryReader.readEntries(
(results) => {
results.forEach(result => {
getCount(result);
})
},
(error) => {
console.log(error);
}
);
}
}
function getFilesFromEntry(entry) {
if (entry.isFile) {
entry.file(
file => {
file.filePath = entry.fullPath.slice(1)
files.push(file)
if (files.length == count) createTree();
},
err => {
console.log(err);
}
)
} else {
const entryReader = entry.createReader()
entryReader.readEntries(
(results) => {
results.forEach(result => {
getFilesFromEntry(result);
})
},
(error) => {
console.log(error);
}
);
}
}
</script>
</body>
</html>