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

Electron React仅从USB路径上传图片及input file定制路径咨询

在Electron React项目中实现仅从USB上传图片的方案

关于原生HTML input[type=file]的限制

原生<input type="file">无法直接定制文件选择窗口的默认路径或限制访问范围。这是浏览器的安全策略,防止网页未经授权访问本地文件系统,所以不能强制它只打开USB路径或禁止访问其他文件夹。

Electron中的实现方案

Electron结合了Node.js的文件系统能力和自身的dialog模块,可以绕过原生input的限制,实现仅从USB选择图片的功能,甚至可以严格校验文件来源。

核心思路

  1. 通过Node.js或系统命令识别当前连接的USB设备路径(跨平台适配)
  2. 使用Electron的dialog.showOpenDialog打开文件选择框,默认指向USB路径
  3. 校验选中的文件是否来自USB路径,过滤非USB文件
  4. 通过IPC通信将文件路径传给React渲染进程,处理上传逻辑

代码实现

1. 主进程(main.js)处理USB路径识别和文件选择

const { app, BrowserWindow, dialog, ipcMain } = require('electron');
const fs = require('fs').promises;
const path = require('path');
const { exec } = require('child_process');

// 获取系统中已连接的USB设备路径(跨平台适配)
async function getUSBDrives() {
  let drives = [];
  switch (process.platform) {
    case 'win32':
      // Windows:通过wmic命令获取可移动磁盘
      return new Promise((resolve) => {
        exec('wmic logicaldisk get caption, drivetype', (err, stdout) => {
          if (err) return resolve([]);
          const lines = stdout.trim().split('\n').slice(1);
          lines.forEach(line => {
            const [caption, driveType] = line.trim().split(/\s+/);
            // 可移动磁盘的类型码为2
            if (driveType === '2') drives.push(caption);
          });
          resolve(drives);
        });
      });
    case 'darwin':
      // macOS:读取/Volumes下的非系统目录
      const volumes = await fs.readdir('/Volumes');
      const excludeVolumes = ['Macintosh HD', 'Boot Camp'];
      drives = volumes
        .filter(vol => !excludeVolumes.includes(vol))
        .map(vol => path.join('/Volumes', vol));
      break;
    case 'linux':
      // Linux:读取/media/用户名目录下的挂载点
      const username = process.env.USER;
      const mediaPath = path.join('/media', username);
      try {
        const mediaDirs = await fs.readdir(mediaPath);
        drives = mediaDirs.map(dir => path.join(mediaPath, dir));
      } catch (err) {
        drives = [];
      }
      break;
    default:
      drives = [];
  }
  return drives;
}

// 监听渲染进程的文件选择请求
ipcMain.handle('open-usb-file-dialog', async () => {
  const usbDrives = await getUSBDrives();
  
  if (usbDrives.length === 0) {
    dialog.showErrorBox('提示', '未检测到已连接的USB设备');
    return [];
  }

  // 打开文件选择框,默认指向第一个USB路径,限制仅选择图片
  const { canceled, filePaths } = await dialog.showOpenDialog({
    defaultPath: usbDrives[0],
    properties: ['openFile', 'multiSelections'],
    filters: [{ name: '图片文件', extensions: ['jpg', 'jpeg', 'png', 'gif', 'bmp'] }]
  });

  if (canceled) return [];

  // 校验所有选中文件是否来自USB路径,过滤非法文件
  const validFiles = filePaths.filter(filePath => {
    return usbDrives.some(drive => filePath.startsWith(drive));
  });

  if (validFiles.length !== filePaths.length) {
    dialog.showErrorBox('错误', '只能选择USB设备中的文件');
    return [];
  }

  return validFiles;
});

// 窗口创建等其他代码...

2. React渲染进程组件(USBImageUpload.jsx)

import { useState } from 'react';
// Electron 14+需在main.js中配置contextIsolation: false,或使用preload脚本
const { ipcRenderer } = window.require('electron');
const path = require('path');

export default function USBImageUpload() {
  const [selectedFiles, setSelectedFiles] = useState([]);

  const handleSelectFiles = async () => {
    try {
      const filePaths = await ipcRenderer.invoke('open-usb-file-dialog');
      if (filePaths.length === 0) return;

      // 将文件路径转换为前端可用的File对象(可选,根据上传需求调整)
      const files = filePaths.map(filePath => {
        const fileName = filePath.split(path.sep).pop();
        // 如果需要上传文件内容,可通过Node.js的fs模块读取后创建File对象
        return new File([], fileName, { type: `image/${fileName.split('.').pop().toLowerCase()}` });
      });

      setSelectedFiles(files);
    } catch (err) {
      console.error('选择文件失败:', err);
    }
  };

  const handleUpload = () => {
    // 实现图片上传逻辑,比如通过fetch发送到后端
    console.log('待上传文件:', selectedFiles);
    // 示例:FormData上传
    const formData = new FormData();
    selectedFiles.forEach(file => formData.append('images', file));
    fetch('/api/upload', { method: 'POST', body: formData });
  };

  return (
    <div className="usb-upload-container">
      <button onClick={handleSelectFiles} className="select-btn">
        从USB选择图片
      </button>
      <div className="file-list">
        {selectedFiles.map((file, idx) => (
          <p key={idx}>{file.name}</p>
        ))}
      </div>
      <button 
        onClick={handleUpload} 
        disabled={selectedFiles.length === 0}
        className="upload-btn"
      >
        上传图片
      </button>
    </div>
  );
}

关键注意事项

  • 跨平台适配:不同系统的USB路径格式差异很大,需要分别处理
  • 权限问题:Electron应用默认拥有文件系统访问权限,开发阶段确保Node.js API可用
  • 严格限制:即使默认打开USB路径,用户仍可手动切换到其他文件夹,通过路径校验可以过滤非USB文件,确保合规性
  • Electron版本:若使用Electron 14及以上版本,建议通过preload脚本暴露IPC方法,避免直接在渲染进程中使用require('electron')

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 04:09:29