js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构

一、实现效果

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

js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构_第1张图片

二、代码实现

由于需要使用树形插件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获取文件夹中的所有文件。

    js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构_第2张图片

     获取的是一个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],
                })
            }
        })
    }

    当前实现效果,有一个根节点了:

    js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构_第3张图片

    (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)
            }
        })
    })

    实现效果:

    js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构_第4张图片

    6、使用input框上传文件夹并且展示成树形结构的功能已经实现了,下边来做拖拽上传文件夹。 

    js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构_第5张图片

     (1)先了解几个拖拽相关API:

    拖拽容器相关事件:

    js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构_第6张图片

     (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);
            }
        }
    })

    打印出来长这样:

    js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构_第7张图片

     可以通过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的原型:

    js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构_第8张图片

    文件夹entry的原型:

     js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构_第9张图片

    (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__);
        }
    }

    js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构_第10张图片

     可以看到当前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
        
    
    
    
        
        
    将文件夹拖到这里进行上传

      你可能感兴趣的:(jquery,前端,javascript)