选择文件夹路径

在浏览器环境中,由于安全限制,无法直接获取目录的完整路径,但可以通过选择目录后提取其路径信息(不含文件名)。

webkitRelativePath:

  1. 纯目录路径提取逻辑
  • 通过 webkitRelativePath 处理webkit内核浏览器(Chrome/Edge/Safari):当选择目录时,该属性会返回 "目录名/文件名" 格式,通过截取第一个 / 之前的部分获取纯目录名
  • 对支持完整路径的环境(如Electron),通过 lastIndexOf('/') 截取目录部分
  • 确保结果中不包含任何文件名
  1. 用户体验优化
  • 添加状态显示已选目录
  • 禁用了文件多选(multiple={false}),更符合目录选择的语义
  1. 浏览器兼容性
  • 主流现代浏览器(Chrome/Edge/Firefox 78+/Safari 11.1+)均支持
  • 注意:在纯网页环境中,浏览器出于安全考虑不会返回完整的本地文件系统路径,只会返回目录名称或相对路径;如果需要完整路径,建议使用Electron等桌面应用框架 使用时,只需将选中的 directoryPath 用于后续业务逻辑即可,该路径已确保不包含任何文件名。
ts 复制代码
import React, { useRef, useState } from 'react';

const DirectoryOnlyPicker: React.FC = () => {
  const directoryInputRef = useRef<HTMLInputElement>(null);
  const [selectedDir, setSelectedDir] = useState<string | null>(null);

  // 处理目录选择,提取纯目录路径
  const handleDirectorySelect = (event: React.ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
    if (!files || files.length === 0) return;

    // 获取目录路径(通过第一个文件的webkitRelativePath推导)
    const firstFile = files[0];
    let directoryPath = '';

    if (firstFile.webkitRelativePath) {
      // webkit浏览器(Chrome/Edge/Safari):通过相对路径提取目录
      const relativePath = firstFile.webkitRelativePath;
      directoryPath = relativePath.substring(0, relativePath.indexOf('/'));
    } else if (firstFile.path) {
      // 部分环境(如Electron)支持直接获取完整路径
      const fullPath = firstFile.path;
      directoryPath = fullPath.substring(0, fullPath.lastIndexOf('/') + 1);
    } else {
      //  fallback:使用目录名称
      directoryPath = firstFile.name;
    }

    setSelectedDir(directoryPath);
    console.log('选中的目录:', directoryPath);
  };

  const openDirectoryPicker = () => {
    if (directoryInputRef.current) {
      directoryInputRef.current.value = '';
      directoryInputRef.current.click();
    }
  };

  return (
    <div style={{ padding: 20 }}>
      <button 
        onClick={openDirectoryPicker}
        style={{
          padding: '8px 16px',
          backgroundColor: '#1890ff',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer',
          fontSize: '14px'
        }}
      >
        选择目录
      </button>

      {selectedDir && (
        <div style={{ marginTop: 10, color: '#333' }}>
          已选目录: <span style={{ fontWeight: 'bold' }}>{selectedDir}</span>
        </div>
      )}

      {/* 隐藏的目录选择输入框 */}
      <input
        ref={directoryInputRef}
        type="file"
        webkitdirectory="true"  // 核心属性:允许选择目录
        directory="true"
        onChange={handleDirectorySelect}
        style={{ display: 'none' }}
        multiple={false}        // 不需要多选文件
      />
    </div>
  );
};

export default DirectoryOnlyPicker;

有坑:用electron打包后,我的电脑能获取到,但是我同事的(win11)获取不到D盘的路径,麻了,好在electron能救命

electron中实现:

复制代码
就是在渲染进程中,触发点击事件后,发送对应的消息到主进程中,然后打开弹窗获取目录这个事教给主进程来做。主进程中实现:
ts 复制代码
// main.js(Electron 主进程文件) 
const { app, BrowserWindow, ipcMain, dialog } = require('electron'); 
const path = require('path'); let mainWindow; 
// 创建窗口函数 
function createWindow() { 
    mainWindow = new BrowserWindow({ 
        width: 1000, height: 800, 
        webPreferences: { 
            // 关键配置:启用上下文隔离(安全默认),指定预加载脚本 
            contextIsolation: true, 
            // 预加载脚本路径(用于暴露 IPC 通信接口,避免直接暴露 Node 模块) 
            preload: path.join(__dirname, 'preload.js'),
            // 禁用 nodeIntegration(安全默认,避免渲染进程直接访问 Node)
            nodeIntegration: false 
        }
    }); 
    // 加载前端页面(根据你的项目路径调整,如 React 打包后的 index.html 或开发环境地址) 
    mainWindow.loadURL('http://localhost:3000'); // 开发环境 
    // mainWindow.loadFile(path.join(__dirname, 'build/index.html')); // 生产环境(打包后) 
} 
// 监听渲染进程的"选择目录"请求 
ipcMain.handle('open-directory-dialog', async () => { 
    try { 
        // 调用 Electron 原生对话框选择目录 
        const result = await dialog.showOpenDialog(mainWindow, { 
            title: '选择目录', 
            properties: ['openDirectory', 'showHiddenFiles', 'createDirectory'], 
            // 支持创建、显示隐藏文件
            defaultPath: 'D:\\' // 默认打开 D 盘(解决原问题:直接定位到非系统盘) 
        }); 
        // 处理结果:若用户未取消,返回选中的目录路径;否则返回 null 
        if (!result.canceled && result.filePaths.length > 0) { 
            return result.filePaths[0]; 
            // 返回第一个选中的目录(目录选择模式下仅一个) 
        } return null; 
    } catch (error) { 
        console.error('主进程目录选择失败:', error);
        return null; 
    } 
}); 
// 应用启动事件 app.whenReady().then(createWindow); 
// 关闭所有窗口时退出应用(Windows/Linux) 
app.on('window-all-closed', () => { 
    if (process.platform !== 'darwin') app.quit(); 
}); 
// Mac 平台: dock 图标点击时重新创建窗口
app.on('activate', () => { 
    if (BrowserWindow.getAllWindows().length === 0) createWindow(); 
});
相关推荐
艾小码2 小时前
还在被超长列表卡到崩溃?3招搞定虚拟滚动,性能直接起飞!
前端·javascript·react.js
闰五月2 小时前
JavaScript作用域与作用域链详解
前端·面试
泉城老铁2 小时前
idea 优化卡顿
前端·后端·敏捷开发
前端康师傅2 小时前
JavaScript 作用域常见问题及解决方案
前端·javascript
司宸2 小时前
Prompt结构化输出:从入门到精通的系统指南
前端
我是日安2 小时前
从零到一打造 Vue3 响应式系统 Day 9 - Effect:调度器实现与应用
前端·vue.js
Mintopia2 小时前
🚀 Next.js 全栈 E2E 测试:Playwright vs Cypress
前端·javascript·next.js
原生高钙2 小时前
JS设计模式指南
前端·javascript