网站访问用户文件夹
在以前,网页是无法获取用户的文件夹隐私,但是现在已经有相关的 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, }
async function pickFile() {
fileHandles = await window.showOpenFilePicker(options)
}
其他选项可用于指示可以选择的文件类型。例如,只想接收 .png
文件,选项对象将包括以下内容
const options = {
types: [
{
description: 'Images',
accept: { 'image/png': '.png', },
},
],
excludeAcceptAllOption: true,
}
多选后返回的结果是一个包含多个文件的数组,因此获取它们的内容将通过以下方式完成:
async function pickFile() {
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
async function writeFile() {
[fileHandle] = await window.showOpenFilePicker()
const file = await fileHandle.getFile()
const writable = await fileHandle.createWritable()
await writable.write('这是新写入的内容')
await writable.close()
}
删除文件
调用 remove()
方法可以删除全部文件或文件夹;传入对应文件名称可以指定删除的文件或文件夹。
async function removeDirectory() {
const directoryHandle = await window.showDirectoryPicker()
await directoryHandle.remove()
}
// 删除选定文件夹中名为data.txt的单个文件
async function removeSelfFile() {
const directoryHandle = await window.showDirectoryPicker()
await directoryHandle.removeEntry('data.txt')
}
// 递归删除名为“data”的文件夹
async function removeAllDirectory() {
const directoryHandle = await window.showDirectoryPicker()
await directoryHandle.removeEntry('data', { recursive: true })
}
浏览器兼容性
