去年开始忽然自开发使用的showDirectoryPicker()这个API选择文件夹上传的功能开始缺失dll文件。找了下原因是Chrome浏览器自动升级导致的。
1. 定位原因
Chrome因为安全策略,dll这种后缀的文件会自动过滤。
目前只发现这种后缀的文件有问题,而且过滤文件用户是无感知的。当文件夹内文件数量几千上万的时候,很容易出错。
2. 解决方案
-
- showDirectoryPicker不再使用,转为不存在兼容问题的input标签的webkitdirectory属性
js
<input
type="file"
multiple
onChange={handleOpenDir}
webkitdirectory="true"
/>
-
- 兼容原来的showDirectoryPicker的后续逻辑
原来获取文件目录结构的代码如下:
js
let dir = await window.showDirectoryPicker();
查看MDN文档,了解到API基于FileSystemDirectoryHandle这个类处理的。
如果改为input方案,则获取的数据需要转换为上面的dir结构,这时候就需要模拟一个上面的类和中转方法,讲input获取到的文件列表转化成上面dir结构
js
const dir = convertFileListToDirectoryHandle(fileList);
convertFileListToDirectoryHandle方法具体如下:
js
function convertFileListToDirectoryHandle(fileList) {
if (!fileList.length) return null;
// 取根目录名(以第一个文件路径的第一个部分为准)
const rootName = fileList[0].webkitRelativePath.split("/")[0];
const rootHandle = new VirtualDirectoryHandle(rootName);
for (const file of fileList) {
const relativePath = file.webkitRelativePath.split("/").slice(1); // 去掉根目录部分
rootHandle.addItem(relativePath, file);
}
return rootHandle;
}
由于不了解FileSystemDirectoryHandle这个类的具体逻辑,将问题抛给AI,chatGPT 或者 Cursor,让他给出一个类,大概率是不能直接用的,然后在这个类的基础上根据自己的需求进行方法和逻辑修改
js
class VirtualFileHandle {
constructor(file) {
this.file = file;
this.kind = "file";
this.name = file.name;
}
async getFile() {
return this.file;
}
}
class VirtualDirectoryHandle {
constructor(name) {
this.name = name;
this.kind = "directory";
this.entriesMap = new Map();
}
async *entries() {
for (const [name, handle] of this.entriesMap) {
yield [name, handle];
}
}
async getFileHandle(name) {
if (
this.entriesMap.has(name) &&
this.entriesMap.get(name).kind === "file"
) {
return this.entriesMap.get(name);
}
throw new Error("File not found");
}
async getDirectoryHandle(name) {
if (
this.entriesMap.has(name) &&
this.entriesMap.get(name).kind === "directory"
) {
return this.entriesMap.get(name);
}
throw new Error("Directory not found");
}
async resolve(handle) {
const pathParts = [];
const findPath = (directory, targetHandle, currentPath) => {
for (const [name, value] of directory.entriesMap) {
if (
value === targetHandle ||
(value instanceof VirtualFileHandle &&
value.file === targetHandle.file)
) {
pathParts.push(...currentPath, name);
return true;
}
if (value instanceof VirtualDirectoryHandle) {
if (findPath(value, targetHandle, [...currentPath, name])) {
return true;
}
}
}
return false;
};
if (findPath(this, handle, [])) {
return pathParts.length > 0 ? pathParts : null;
}
return null; // 未找到
}
addItem(pathParts, file) {
let currentDir = this;
for (let i = 0; i < pathParts.length; i++) {
const part = pathParts[i];
if (i === pathParts.length - 1) {
// 最后一个是文件
currentDir.entriesMap.set(part, new VirtualFileHandle(file));
} else {
// 目录
if (!currentDir.entriesMap.has(part)) {
currentDir.entriesMap.set(part, new VirtualDirectoryHandle(part));
}
currentDir = currentDir.entriesMap.get(part);
}
}
}
}
export default VirtualDirectoryHandle;