只需百来行代码,为你的Web页面增加本地文件操作能力,确定不试试吗?

简介: 笔者开源了一个`Web`思维导图[mind-map](https://github.com/wanglin2/mind-map),数据默认是存储在`localstorage`里,如果想保存到本地文件,需要使用导出功能,下次打开再使用导入功能,编辑完如果又想保存到文件,那么又需要从重新导出覆盖原来的文件,不得不说,可以但不优雅,所以最近增加了直接编辑本地文件的能力,体验了一下,还是不错的,并且就是调调`API`的事情,很简单,何乐而不为。

笔者开源了一个Web思维导图mind-map,数据默认是存储在localstorage里,如果想保存到本地文件,需要使用导出功能,下次打开再使用导入功能,编辑完如果又想保存到文件,那么又需要从重新导出覆盖原来的文件,不得不说,可以但不优雅,所以最近增加了直接编辑本地文件的能力,体验了一下,还是不错的,并且就是调调API的事情,很简单,何乐而不为。

主角就是showOpenFilePickershowSaveFilePicker两个API,笔者基于它俩开发了三个功能:

新建另存为其实一样的,只不过一个保存的是空数据,一个是当前的数据,当创建或打开文件成功后,操作的时候数据会直接保存到本地文件里,不再需要进行手动的导出,这种体验其实就和本地编辑器没什么区别了。

打开

先来看看打开文件,调用的是showSaveFilePicker方法,返回一个Promise,选择文件成功了那么Promise的结果是一个数组,每一项代表一个文件的操作句柄:

如果要获取某个文件的内容或写入某个文件就需要通过这些文件句柄对象。如果没有选择或选择失败了Promise则会出错:

这个方法接收一个选项对象作为参数:

  • options.multiple

布尔值,设置是否可以选择多个文件。

  • options.types

一个数组,设置允许被选择的文件类型,数组每一项都是一个对象:

{
    description: '',
    accept: {
        '': []
    }
}

description用于说明,好像没什么用,accept是个对象,keyMIME typevalue为一个数组,代表允许的文件扩展名。

如果MIME type设置的很具体,比如application/json,那么value不传的话只能选择文件后缀为.json的文件,如果value设置了扩展名的话,则在默认的.json文件外还允许选择设置的扩展名的文件,比如设置为['.smm'],那么.json.smm为后缀的文件都可以选择:

如果MIME type设置的比较宽泛的话,比如application/*,那么所有MIME typeapplication类型的文件都可以选择,就算value只设置了一个.json,其他类型的文件也是可以选择的,所以value的作用不是限制,而是扩充。

但是呢,这种限制可以轻松突破,只要点击扩展名打开下拉列表选择所有文件选项,那么还是想选什么文件就选什么文件,有朋友知道怎么解决的欢迎评论区留言。

  • options.excludeAcceptAllOption

布尔值,默认为false,即允许不配置types选项,支持选择所有文件,如果设为true,那么types选项不能为空,必须要限制一种文件类型。

笔者的思维导图文件格式使用的是.json,并且吃饱了撑的自己定义了一个格式.smm,其实就是json,并且同一时间只能编辑一个文件,那么打开文件的代码如下所示:

let fileHandle = null
async openLocalFile() {
    try {
        let [ _fileHandle ] = await window.showOpenFilePicker({
            types: [
                {
                    description: '',
                    accept: {
                        'application/json': ['.smm']
                    }
                },
            ],
            excludeAcceptAllOption: true,
            multiple: false
        });
        if (!_fileHandle) {
            return
        }
        fileHandle = _fileHandle
        if (fileHandle.kind === 'directory') {
            this.$message.warning('请选择文件')
            return
        }
        this.readFile()
    } catch (error) {
        if (error.toString().includes('aborted')) {
            return
        }
        this.$message.warning('你的浏览器可能不支持哦')
    }
}

将文件句柄保存起来,接下来都会基于它来操作文件,先来看看文件句柄对象,它存在两个方法:

  • getFile()

返回一个Promise,获取该句柄所对应的文件对象,其实就是我们常见的File对象:

  • createWritable()

返回也是一个Promise,创建一个可以写入文件的文件流对象:

基于这两个方法我们就可以读取打开文件的内容及把新内容写入文件:

// 读取文件
async readFile() {
    let file = await fileHandle.getFile();
    let fileReader = new FileReader();
    fileReader.onload = async () => {
        // fileReader.result
    }
    fileReader.readAsText(file);
}

// 写入文件
async writeLocalFile(content) {
    if (!fileHandle) {
        return;
    }
    let string = JSON.stringify(content);
    const writable = await fileHandle.createWritable();
    await writable.write(string);
    await writable.close();
}

页面内第一次调用createWritable方法浏览器会弹个窗询问用户是否允许:

每调用一次createWritable方法都会在你的本地创建一个.crswap文件:

相当于一个临时文件,没有调用写入流writableclose方法前,调用它的write方法写入的内容默认都保存在这个文件,只有调用close以后才会更新到源文件,并且自动删除这个临时文件,另外页面关闭,也会删除这些文件。

写入流默认是空的,每调用一次write方法,都会在.crswap中追加内容,但是可以指定写入的位置:

await writable.write({ type: "write", position: 0, data: string });

这样会从指定的字节数开始写入,注意是替换,而不是插入。

所以为了方便起见,最好还是创建、写入就关闭,再写再创建。

新建

新建调用的是showSaveFilePicker方法,也接收一个选项对象为参数,有两个选项和showOpenFilePicker方法是一样的,即typesexcludeAcceptAllOption,之外还有一个选项:

  • suggestedName

默认填充的文件名称,为空则创建文件时输入框就是空的。

可以直接输入文件名创建新文件,也可以点击已经存在的文件进行替换。

创建成功返回的也是一个文件句柄,那么创建文件就很简单了:

async createLocalFile(content) {
    try {
        let _fileHandle = await window.showSaveFilePicker({
            types: [{
                description: '',
                accept: {'application/json': ['.smm']},
            }],
            suggestedName: '思维导图'
        });
        if (!_fileHandle) {
            return;
        }
        const loading = this.$loading({
            lock: true,
            text: '正在创建文件',
            spinner: 'el-icon-loading',
            background: 'rgba(0, 0, 0, 0.7)'
        });
        fileHandle = _fileHandle;
        await this.writeLocalFile(content);
        await this.readFile();
        loading.close();
    } catch (error) {
        if (error.toString().includes('aborted')) {
            return
        }
        this.$message.warning('你的浏览器可能不支持哦');
    }
}

来看看实际效果:

总结

最后再来看看兼容性:

因为目前还是实验性质,所以可以看到是一片红,但是因为我的本身也只是一个示例项目,所以问题不大,有胜于无。

另外这个特性目前也只能在HTTPS协议或localhost下才可用,其他情况下window对象是不存在这两个API的,所以需要做好错误处理。

相关文章
|
23天前
|
存储
在 Web 中判断页面是不是刷新
【9月更文挑战第10天】在Web开发中,判断页面是否刷新有多种方法:1) 监听`popstate`事件,检测用户是否通过历史记录访问页面;2) 记录并比较页面加载时间戳,若相差极小,则可能为刷新;3) 利用本地存储设置特定值,若该值不存在或不符合预期,则页面可能被刷新。然而,这些方法并非绝对准确。
|
2月前
|
开发框架 前端开发 Java
【前端学java】SpringBootWeb极速入门-实现一个简单的web页面01
【8月更文挑战第12天】SpringBootWeb极速入门-实现一个简单的web页面01
53 3
【前端学java】SpringBootWeb极速入门-实现一个简单的web页面01
html,web页面朗读文字,朗读中文,朗读英文
html,web页面朗读文字,朗读中文,朗读英文
|
2月前
|
数据处理 开发者 UED
FastAPI 的模板引擎简直太神奇啦!这就是构建动态 Web 页面的终极秘籍,快来一探究竟!
【8月更文挑战第31天】FastAPI 是一款高性能异步 Web 框架,可通过集成模板引擎(如 Jinja2 或 Mako)实现动态页面渲染。使用模板引擎可分离页面结构与数据,简化代码并提升可维护性。此外,它还提供丰富的语法支持,如循环和条件判断,从而增强页面展示效果及开发效率。通过简单的配置步骤,即可在 FastAPI 中启用模板引擎,显著改善用户体验。
142 1
|
21天前
|
数据处理 Python
Django视图:构建动态Web页面的核心技术
Django视图:构建动态Web页面的核心技术
|
2月前
|
XML JavaScript 测试技术
Web自动化测试框架(基础篇)--HTML页面元素和DOM对象
本文为Web自动化测试入门指南,介绍了HTML页面元素和DOM对象的基础知识,以及如何使用Python中的Selenium WebDriver进行元素定位、操作和等待机制,旨在帮助初学者理解Web自动化测试中的关键概念和操作技巧。
40 1
|
2月前
|
缓存 运维 网络协议
一台新PC进行Web页面请求的历程:技术深度剖析
【8月更文挑战第24天】在当今数字化时代,当我们轻轻点击浏览器上的一个链接,背后其实经历了一场复杂而精妙的交互过程。本文将带您深入探索,从一台全新PC的角度出发,揭秘Web页面请求的全过程,展现这背后隐藏的技术奥秘。
28 0
|
2月前
【Azure 应用服务】Web.config中设置域名访问限制,IP地址限制访问特定的页面资源 (Rewrite)
【Azure 应用服务】Web.config中设置域名访问限制,IP地址限制访问特定的页面资源 (Rewrite)
|
2月前
|
开发框架 前端开发 API
使用代码生成工具快速开发应用-结合后端Web API提供接口和前端页面快速生成,实现通用的业务编码规则管理
使用代码生成工具快速开发应用-结合后端Web API提供接口和前端页面快速生成,实现通用的业务编码规则管理
|
4月前
|
缓存 JavaScript 前端开发
程序员必知:广告等第三方应用嵌入到web页面方案之使用js片段
程序员必知:广告等第三方应用嵌入到web页面方案之使用js片段
51 0
下一篇
无影云桌面