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

2023-05-16

一、实现效果

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

二、代码实现

由于需要使用树形插件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>

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 上电浪涌电流

    上电浪涌电流 电机启动或者停转都会形成浪涌电流 xff0c 例如启动的浪涌最大 xff0c 毕竟电机启动静态电阻非常小 xff0c 上电等同短路 xff0c 其电机为感性负载 xff0c 由较大的无功电流 xff0c 对电网造成波动非常大
  • 电机功率计算公式

    电机功率计算公式 电动机输入功率 单相电机为P 61 UI xff0c 三相电机P 61 UIcos0 8 输出功率 xff08 驱动功率 xff09 P 61 FV F为力 牛顿 V xff1a 速度 m S xff09 换算到电机则有
  • C++ 中 map 字典与 set 集合的使用

    在 C 43 43 中 xff0c map 是关联容器 的一种 xff0c 关联容器将值 与键 关联到一起 xff0c 并使用键来查找值 这与 python 中的字典 类型类似 xff0c 也是用来存储键 值对 xff08 key valu
  • win11 安装 WSL2 在非 C 盘及配置(图形界面+代理)

    WSL 安装及配置 直接安装 WSL2 在非 C 盘启用 WSL 功能前提条件设置默认安装 WSL2安装在非 C 盘 图形界面先决条件更新 WSL 以支持 GUI 配置 WSL2 使用 Windows 网络代理 直接安装 WSL2 在非 C
  • CVTE嵌入式实习生与秋招

    目录 前言一 实习笔试二 实习面试三 实习工作内容四 公司看法 前言 今年暑假去CVTE实习了一个多月最后经过转正答辩 xff0c 获得了offer xff0c 现就我的实习经历和对公司的一些认知分享一下 xff08 仅代表个人观点 xff
  • 视频编解码行业及发展方向简述

    目录 一 视频行业1 视频是一个方兴未艾的大产业2 视频行业潜在商机大 人才缺口大3 了解华为海思的HI3518E方案 二 海思方案项目用到的硬件平台介绍1 本专栏文章使用的开发板配置2 处理器为什么选HI3518E 三 本专栏文章规划和核
  • 全面认识海思SDK及嵌入式层开发(1)

    目录 一 全面认识和检测配套开发套装1 套装配件介绍2 检测开发板3 注意 二 视频设备开发的技术流1 视频从产生到被消费的整个流程2 视频行业的商业角度分段3 几个疑问点 一 全面认识和检测配套开发套装 购买方式 xff1a 淘宝搜索 g
  • 嵌入式linux开发环境搭建(VMware16.0.0+Ubuntu16.04.3_X64)

    目录 一 安装VMware1 VMware介绍2 安装VMware16 0 0 二 安装ubuntu16 04 3 LTS1 Ubuntu介绍2 下载安装包iso3 安装 四 新安装Ubuntu的基本设置1 开机和关机等2 虚拟机基本设置3
  • 全面认识海思SDK及嵌入式层开发(2)

    目录 一 HI3518E方案系统整体架构介绍1 硬件上2 软件上 二 海思SDK的整体介绍三 海思SDK包的学习和实验1 2篇相关文档2 SDK包复制到linux原生目录中并解压3 SDK包操作的脚本程序研究4 SDK中源码包部分的配置编译
  • 计算机视觉之相机模型

    目录 一 相机模型1 相机与图像2 坐标系3 世界坐标系到摄像机坐标系4 摄像机坐标系到图像物理坐标系5 图像物理坐标系到图像像素坐标系6 摄像机坐标系到图像像素坐标系7 世界坐标系到图像像素坐标系 二 镜头畸变1 相机成像原理2 镜头畸变
  • vscode安装插件失败,完美解决

    vscode安装插件一直失败 xff0c 解决方案如下 访问vscode插件官网https marketplace visualstudio com vscode xff0c 搜索你要的插件点击插件详情 Version History 下载
  • ROS的topic通信机制

    1 通信步骤如图 xff1a 2 步骤介绍 第 xff08 0 xff09 步 xff1a talker gt master 发布者talker向mater注册 xff1a 包括节点的信息 需要发布的话题名等 xff0c 然后节点管理器RO
  • 关于快速幂和矩阵快速幂

    快速幂 xff1a 可参考该链接百科快速幂也可以参考这个博客快速幂博客 给出快速幂的题目和代码 xff1a 快速幂 取余计算 include lt iostream gt include lt string h gt using names
  • C/C++ 文件操作基础

    目录 1 文件分类 2 文件的打开与关闭 3 顺序读写文件 4 随机读写文件 5 其他与文件相关的操作 最近看 GNU Radio 源码看到了文件操作的部分 xff0c 因此记录下学习 C 43 43 C 操作文件的过程 本文的文件操作是
  • npm的装包原理

    原文 xff1a npm安装包原理 前言 xff1a 提起npm xff0c 大家第一个想到的应该就是 npm install 了 xff0c 但是 npm install 之后生成的 node modules大家有观察过吗 xff1f p
  • 02基于freertos实现串口通讯

    文章目录 一 操作步骤1 任务创建API2 步骤 二 代码 一 操作步骤 1 任务创建API 动态创建任务xTaskCreate 静态创建任务xTaskCreateStatic 任务句柄 xff1a 任务句柄就是一个指针 xff0c 指向任
  • windows中freeRTOS模拟器环境搭建

    windows中的freeRTOS使用 FreeRTOS参考手册中的模拟程序都是基于windows版本的模拟器 因此为了学习FreeRTOS xff0c 需要在Windows中搭建一下模拟器的运行环境 网络上的一般都是直接跑一下FreeRT
  • Xavier NX+4路Cameralink图像处理卡

    Xavier NX 43 4路Cameralink图像处理卡 M D CAP是天津雷航光电科技有限公司推出的一款复合加速计算平台 xff0c 由 NVidia GPU 和 Xilinx FPGA 通过PCIE 互联而成的高性能计算平台 其中
  • Xavier NX+KU040图像处理卡

    Xavier NX 43 KU040图像处理卡 CS NX21T是天津雷航光电科技有限公司推出的一款复合加速计算平台 xff0c 由 NVidia GPU 和 Xilinx FPGA 通过PCIE 互联而成的高性能计算平台 其中 FPGA
  • js使用input上传文件夹、拖拽上传文件夹并将文件夹结构展示为树形结构

    一 实现效果 左侧区域支持选择一个系统中的文件夹 xff0c 或者将文件夹拖拽到这个区域进行上传 xff0c 右侧区域可以将文件夹的结构展示为树形结构 二 代码实现 由于需要使用树形插件zTree xff0c 这个插件是依赖于jquery的