网站访问用户文件夹
刀刀
1/8/2025
0 字
0 分钟
在以前,网页是无法获取用户的文件夹隐私,但是现在已经有相关的 API 了。下面通过几步来实现。
读取文件夹内全部文件
选择文件夹
通过 showDirectoryPicker
API 打开选择文件夹对话框,调用该方法就会弹窗对话框,在对话框内可以选择文件夹。
选择完毕后需要用户确认给予相关权限,确认后就能生效。代码如下:
btn.onclick= function() {
console.log('click me')
showDirectoryPicker()
}
得到文件夹内的文件、子文件夹
showDirectoryPicker
API 的返回值就是得到的文件或文件夹的句柄,返回如下:
{
kind: 'directory',
name:'xxx'
}
其中,kind
表示得到的类型,directory
表示文件夹,file
表示文件。name
为该文件夹或文件的名称。
获取有可能会失败,可能用户会拒绝访问文件夹,解决方法为通过 try...catch
捕获错误。
拿到了文件夹时,可以通过 .entries()
方法获取文件夹内所有的内容,返回值是一个异得到的是步迭代器,通过 for await of
循环都可以拿到子文件句柄。句柄返回的内容如下:
[
'foldr-request',
{
kind: 'directory',
name: 'folder-request'
}
],
[
'demo.html',
{
kind: 'file',
name: 'demo.html'
}
]
由此可见能拿到文件夹或文件,可以通过递归的形式一直去循环遍历获取句柄,直到全部都获取到文件为止。代码如下:
查看代码
btn.onclick = async function() {
try {
const handle = await showDirectoryPicker()
const root = await processHandle(handle)
} catch(err) {
// ...
}
}
async function processHandle(handle) {
// 添加判断,终止递归,返回文件
if(handle.kind === 'file') {
return handle
}
handle.children = []
const iter = await handle.entries() // 获取文件夹中所有内容
for await (const info of iter) {
const subHandle = await processHandle(info[1]) // 返回的是一个数组,返回的内容格式如上所述。通过递归的思想一直获取文件夹内的内容
handle.children.push(subHandle)
}
return handle
}
现在 root
能够拿到一个树状结构的句柄。
读取文件
通过 getFile()
可以获取文件内容,使用 FileReader()
类中的 readAsText
方法就能读取文件内容,该读取操作是异步操作,通过 onload
函数能够最终获取结果。代码如下:
btn.onclick = async function() {
try {
const handle = await showDirectoryPicker()
const root = await processHandle(handle)
// 获取内容
const file = await root.children[1].getFile()
const reader = new FileReader()
reader.onload = e => {
console.log(e.target.result)
}
} catch(err) {
// ...
}
}
// ...
其他操作
文件夹多选
要从多个文件中读取,我们需要传入一个 options
对象给 showOpenFilePicker()
,其中,默认情况下,multiple
属性设置为 false
,想要多选需要修改为 true
。
let fileHandles;
const options = { multiple: true, };
const pickFile = async () => {
fileHandles = await window.showOpenFilePicker(options);
};
其他选项可用于指示可以选择的文件类型。例如,只想接收 .png
文件,选项对象将包括以下内容
const options = {
types: [
{
description: "Images",
accept: { "image/png": ".png", },
},
],
excludeAcceptAllOption: true,
};
多选后返回的结果是一个包含多个文件的数组,因此获取它们的内容将通过以下方式完成:
const pickFile = async () => {
const fileList = await window.showOpenFilePicker({ multiple: true });
const allContent = await Promise.all( fileList.map(async (fileItem) => {
const file = await fileItem.getFile();
const content = await file.text();
return content;
})
);
console.log(allContent);
};
写文件
写文件的操作分为以下几步:
- 通过
createWritable()
返回一个FileSystemWritableFileStream
对象 - 调用
write()
将需要传递内容写入此流 - 调用
close()
关闭流
btn.onclick = async function() {
try {
const handle = await showDirectoryPicker({
types: [
{
description: "Test files",
accept: {
"text/plain": [".txt"],
},
},
],
})
// 写文件
const writable = await handle.createWritable()
await writable.write('hello world')
await writable.close()
} catch(err) {
// ...
}
}
这样就能写一个 html
内容。
编辑文件内容
编辑文件的本质是先通过 showOpenFilePicker()
和 getFile()
方法读取文件,然后使用createWritable()
, write()
并 close()
写入同一个文件内,实现编辑操作。
let fileHandle;
const writeFile = async () => {
[fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const writable = await fileHandle.createWritable();
await writable.write("这是新写入的内容");
await writable.close();
};
删除文件
调用 remove()
方法可以删除全部文件或文件夹;传入对应文件名称可以指定删除的文件或文件夹。
const removeDirectory = async () => {
const directoryHandle = await window.showDirectoryPicker();
await directoryHandle.remove();
};
// 删除选定文件夹中名为data.txt的单个文件
const removeSelfFile = async () => {
const directoryHandle = await window.showDirectoryPicker();
await directoryHandle.removeEntry("data.txt");
};
// 递归删除名为“data”的文件夹
const removeAllDirectory = async () => {
const directoryHandle = await window.showDirectoryPicker();
await directoryHandle.removeEntry('data', { recursive: true });
};