一、实现效果

左侧区域支持选择一个系统中的文件夹,或者将文件夹拖拽到这个区域进行上传,右侧区域可以将文件夹的结构展示为树形结构。

二、代码实现

由于需要使用树形插件zTree,这个插件是依赖于jquery的,所以在项目中我们需要引入:

1、jquery

2、zTree:官网链接API Document [zTree -- jQuery tree plug-ins.]

项目结构很简单,一个zTree源码的文件夹,一个index.html文件

下载完zTree源码之后,解压放到index.html平级的位置

3、在index.html的head标签中引入相关依赖

4、搭建html结构,左边放置拖拽框和input输入框,右边放置树结构

将文件夹拖到这里进行上传

    5、input框上传文件夹实现

    input框可以增加一个属性webkitdirectory实现上传文件夹,不过这个功能过低版本的浏览器是不兼容的。使用onchange事件监听这个input框的输入事件,就可以在上传文件夹完毕通过event.target.files获取文件夹中的所有文件。

     获取的是一个fileList,是一个类数组对象,每一个元素是一个file信息,其中包含文件更新时间、文件名、大小、类型、相对路径。

    我们需要解析文件的相对路径,从而获取真正的文件夹结构。

    首先需要使用Array.from()方法将类数组对象转换为数组,这样就可以使用数组的迭代方法。

    具体的解析过程放在createTree方法中

    我们对文件的相对路径进行解析,最终是要放在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方法创建文件结构树。

    至此拖拽功能实现

    完整代码:

    Document

    将文件夹拖到这里进行上传

      参考链接

      评论可见,请评论后查看内容,谢谢!!!
       您阅读本篇文章共花了: