文件操作:showDirectoryPicker

212 阅读2分钟

概述

在传统的前端开发中,处理用户文件一直是个棘手的问题。虽然 <input type="file"> 提供了基本的文件选择功能,但当需要处理整个目录结构时,开发者往往需要借助复杂的后端支持或Electron等桌面框架。showDirectoryPicker() 的出现彻底改变了这一局面,它为Web应用提供了原生的目录访问能力,文件操作体验。

showDirectoryPicker

showDirectoryPicker() 是 File System Access API 的核心方法之一,它允许Web应用通过用户授权的方式访问整个目录结构,而不仅仅是单个文件。这意味着开发者现在可以在浏览器中实现以前只能在桌面应用中才能完成的文件操作。

核心特性

  • 完整的目录访问:读取、遍历、修改目录内容
  • 权限持久化:用户授权后可保存访问权限
  • 安全沙箱:在严格的用户控制下运行
  • 现代化API:基于Promise的异步设计

浏览器支持情况

只有部分浏览器支持,并且版本比较新,希望以后能够更好支持 image.png

基础使用与语法

 基本调用方式

async function selectDirectory() {
  try {
    const directoryHandle = await window.showDirectoryPicker();
    console.log('选择的目录:', directoryHandle.name);
    return directoryHandle;
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('用户取消了选择');
    } else {
      console.error('发生错误:', err);
    }
  }
}

配置选项

const options = {
  id: 'projectFolder', // 标识符,用于记住用户选择
  mode: 'readwrite',   // 权限模式:read 或 readwrite
  startIn: 'documents' // 起始目录:desktop, documents, downloads等
};

const directoryHandle = await showDirectoryPicker(options);

权限模式

  • read :仅读取权限,可以列出文件和读取内容
  • readwrite :读写权限,可以创建、修改、删除文件

实际应用场景

遍历文件内容

async function* walkDirectory(directoryHandle, path = '') {
  for await (const entry of directoryHandle.values()) {
    const entryPath = `${path}/${entry.name}`;
    
    if (entry.kind === 'directory') {
      yield* await walkDirectory(entry, entryPath);
    } else {
      yield {
        handle: entry,
        path: entryPath,
        kind: 'file'
      };
    }
  }
}

// 使用示例
async function printDirectoryTree() {
  const directoryHandle = await showDirectoryPicker();
  
  for await (const entry of walkDirectory(directoryHandle)) {
    console.log(entry.path);
  }
}

printDirectoryTree()

解析本地文件夹可视化到线上

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <button>打开文件</button>


    <script>

        const btn = document.querySelector('button');

        btn.addEventListener('click', async () => {

            async function readDirectoryRecursive(directoryHandle, path = '') {
                const results = {
                    path: path || directoryHandle.name,
                    files: [],
                    directories: [],
                    complete: false,
                    totalFiles: 0
                };

                try {
                    for await (const entry of directoryHandle.values()) {
                        const entryPath = `${path}/${entry.name}`;

                        if (entry.kind === 'file') {
                            results.files.push({
                                name: entry.name,
                                path: entryPath,
                                handle: entry
                            });
                            results.totalFiles++;

                        } else if (entry.kind === 'directory') {
                            // 递归读取子目录
                            const subDirResult = await readDirectoryRecursive(entry, entryPath);
                            results.directories.push(subDirResult);
                            results.totalFiles += subDirResult.totalFiles;
                        }
                    }

                    results.complete = true;
                    return results;

                } catch (error) {
                    results.error = error;
                    results.complete = false;
                    throw error;
                }
            }

            // 使用示例
            async function main() {
                const directoryHandle = await showDirectoryPicker();
                const result = await readDirectoryRecursive(directoryHandle);

                if (result.complete) {
                    console.log(`递归读取完成,共找到 ${result.totalFiles} 个文件`);
                    console.log('目录结构:', result);

                    processFile(result);
                    async function processFile(result) {
                        if (result.files && result.files.length) {
                            for (let i = 0; i < result.files.length; i++) {
                                const fileHandle = result.files[i].handle;
                                const file = await fileHandle.getFile();
                                console.log("file----", file);
                                const fileReader = new FileReader();

                                fileReader.onload = (e) => {
                                    console.log("fileReader.result---", e.target.result);
                                }
                                fileReader.readAsText(file);
                            }
                        }

                        if (result.directories && result.directories.length) {
                            for (let i = 0; i < result.directories.length; i++) {
                                const dirRes = result.directories[i];
                                processFile(dirRes);
                            }
                        }

                    }
                }
            }
            main()




        });
    </script>
</body>

</html>