You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过浏览器File System Access API获取客户端目录文件列表

基于File System Access API实现目录选择与树形视图渲染

核心实现步骤

1. 触发目录选择

首先需要绑定用户交互事件(比如按钮点击)调用目录选择接口,这个API必须由用户主动手势触发,不能自动弹出选择框:

<button id="selectDirBtn">选择本地目录</button>
<div id="treeContainer"></div>
const selectDirBtn = document.getElementById('selectDirBtn');
const treeContainer = document.getElementById('treeContainer');

selectDirBtn.addEventListener('click', async () => {
  // 先做兼容性判断
  if (!('showDirectoryPicker' in window)) {
    alert('当前浏览器不支持File System Access API,请使用最新版Chrome/Edge浏览器');
    return;
  }
  try {
    // 弹出目录选择框,用户授权后拿到根目录句柄
    const rootDirHandle = await window.showDirectoryPicker();
    // 递归遍历生成目录树结构
    const treeData = await traverseHandle(rootDirHandle);
    // 渲染树形视图
    treeContainer.innerHTML = '';
    const rootList = document.createElement('ul');
    rootList.appendChild(renderTree(treeData));
    treeContainer.appendChild(rootList);
  } catch (err) {
    // 用户取消选择会抛错,直接忽略即可
    if (err.name !== 'AbortError') console.error(err);
  }
});

2. 递归遍历目录生成结构化数据

拿到目录句柄后,递归遍历所有子项,区分文件和子目录,生成统一的树结构数据:

/**
 * 递归遍历文件/目录句柄,生成树节点
 * @param {FileSystemHandle} handle 文件/目录句柄
 * @returns 树节点对象
 */
async function traverseHandle(handle) {
  const node = {
    name: handle.name,
    kind: handle.kind, // 'file' 或 'directory'
    handle: handle
  };

  if (handle.kind === 'directory') {
    node.children = [];
    // 异步遍历目录下的所有条目
    for await (const childHandle of handle.values()) {
      const childNode = await traverseHandle(childHandle);
      node.children.push(childNode);
    }
  }

  return node;
}

3. 递归渲染树形视图

基于生成的树结构数据,递归生成DOM节点,可按需添加折叠/展开交互:

/**
 * 递归渲染树节点为DOM
 * @param {object} node 树节点
 * @returns DOM元素
 */
function renderTree(node) {
  const li = document.createElement('li');
  li.style.listStyle = 'none';
  const contentSpan = document.createElement('span');
  li.appendChild(contentSpan);

  if (node.kind === 'directory') {
    // 目录节点添加折叠交互
    contentSpan.style.cursor = 'pointer';
    const childList = document.createElement('ul');
    childList.style.paddingLeft = '20px';
    childList.style.margin = '4px 0';
    // 默认展开
    let isCollapsed = false;

    contentSpan.addEventListener('click', () => {
      isCollapsed = !isCollapsed;
      childList.style.display = isCollapsed ? 'none' : 'block';
      contentSpan.textContent = isCollapsed ? `▶ ${node.name}` : `▼ ${node.name}`;
    });

    // 初始标记为展开状态
    contentSpan.textContent = `▼ ${node.name}`;

    // 渲染所有子节点
    node.children.forEach(child => {
      childList.appendChild(renderTree(child));
    });
    li.appendChild(childList);
  } else {
    // 文件节点加文件标记
    contentSpan.textContent = `📄 ${node.name}`;
  }

  return li;
}

注意事项

  • 兼容性:该API目前仅支持Chromium内核浏览器(Chrome 86+、Edge 86+),Firefox、Safari默认未开放该能力,必须做兼容兜底。
  • 权限边界:用户授权目录后,仅能访问用户所选目录范围内的文件,无法越权访问其他路径;如果需要读取文件内容,可在拿到文件句柄后调用getFile()方法获取File对象,和原生<input type="file">拿到的文件对象用法一致。
  • 生命周期:页面刷新、关闭后,之前获取的文件/目录句柄会失效,需要用户重新选择授权,无法持久化保留访问权限。
  • 性能:如果选择的目录包含上万级别的文件,递归遍历会有一定耗时,大目录场景建议加加载状态提示,避免页面假死。

内容的提问来源于stack exchange,提问作者Atul Bhatt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 23:33:29