前端实现对本地文件的IO操作

前言

在网页中,前端已经可以读取本地文件系统,对本地的文件进行IO读写,甚至可以制作一个简单的VScode编辑器。这篇文章以渐进式方式实现此功能,文末附上所有代码。

首先看整体功能演示
在这里插入图片描述

功能概述

我们将实现一个简单的 Web 应用,具备以下功能:

  1. 选择本地目录:用户可以选择本地目录并显示其结构。
  2. 文件浏览:用户可以浏览目录中的文件和子目录。
  3. 文件编辑:用户可以选择文件并在网页上进行编辑。
  4. 文件保存:用户可以将编辑后的文件保存到本地。

核心实现步骤

我们将功能拆分为以下几个核心步骤:

  1. 选择本地目录
  2. 构建文件树
  3. 读取和编辑文件
  4. 保存编辑后的文件
1. 选择本地目录

选择本地目录是实现这个功能的第一步。我们使用 File System Access API 的 showDirectoryPicker 方法来选择目录。

document.getElementById('selectDirectoryButton').addEventListener('click', async function() {try {const directoryHandle = await window.showDirectoryPicker();console.log(directoryHandle);  // 打印目录句柄} catch (error) {console.error('Error: ', error);}
});
2. 构建文件树

选择目录后,我们需要递归地构建文件树,并在页面上显示文件和子目录。

async function buildFileTree(directoryHandle, parentElement) {for await (const [name, entryHandle] of directoryHandle.entries()) {const li = document.createElement('li');li.textContent = name;if (entryHandle.kind === 'file') {li.classList.add('file');li.addEventListener('click', async function() {currentFileHandle = entryHandle;const file = await entryHandle.getFile();const fileContent = await file.text();document.getElementById('fileContent').textContent = fileContent;document.getElementById('editArea').value = fileContent;document.getElementById('editArea').style.display = 'block';document.getElementById('saveButton').style.display = 'block';});} else if (entryHandle.kind === 'directory') {li.classList.add('folder');const ul = document.createElement('ul');ul.style.display = 'none';li.appendChild(ul);li.addEventListener('click', function() {ul.style.display = ul.style.display === 'none' ? 'block' : 'none';});await buildFileTree(entryHandle, ul);}parentElement.appendChild(li);}
}
3. 读取和编辑文件

当用户点击文件时,我们读取文件内容,并在文本区域中显示以便编辑。

li.addEventListener('click', async function() {currentFileHandle = entryHandle;const file = await entryHandle.getFile();const fileContent = await file.text();document.getElementById('fileContent').textContent = fileContent;document.getElementById('editArea').value = fileContent;document.getElementById('editArea').style.display = 'block';document.getElementById('saveButton').style.display = 'block';
});
4. 保存编辑后的文件

编辑完成后,用户可以点击保存按钮将修改后的文件内容保存回本地文件。

document.getElementById('saveButton').addEventListener('click', async function() {if (currentFileHandle) {const editArea = document.getElementById('editArea');const updatedContent = editArea.value;// 创建一个 writable 流并写入编辑后的文件内容const writable = await currentFileHandle.createWritable();await writable.write(updatedContent);await writable.close();// 更新显示区域的内容document.getElementById('fileContent').textContent = updatedContent;}
});

核心 API 介绍

window.showDirectoryPicker()

该方法打开目录选择对话框,并返回一个 FileSystemDirectoryHandle 对象,代表用户选择的目录。

const directoryHandle = await window.showDirectoryPicker();
FileSystemDirectoryHandle.entries()

该方法返回一个异步迭代器,用于遍历目录中的所有文件和子目录。

for await (const [name, entryHandle] of directoryHandle.entries()) {// 处理每个文件或目录
}
FileSystemFileHandle.getFile()

该方法返回一个 File 对象,表示文件的内容。

const file = await fileHandle.getFile();
FileSystemFileHandle.createWritable()

该方法创建一个可写流,用于写入文件内容。

const writable = await fileHandle.createWritable();
await writable.write(content);
await writable.close();

总结

通过以上步骤,我们能够选择本地目录、浏览文件和子目录、读取和编辑文件内容,并将编辑后的文件保存回本地。同时,我们使用 Highlight.js 实现了代码高亮显示。

源码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Local File Browser with Edit and Save</title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/atom-one-dark.min.css"><script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/highlight.min.js"></script><style>body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;margin: 0;padding: 0;display: flex;height: 100vh;overflow: hidden;background-color: #2e2e2e;color: #f1f1f1;}#sidebar {width: 20%;background-color: #333;border-right: 1px solid #444;padding: 20px;box-sizing: border-box;overflow-y: auto;}#content {width: 40%;padding: 20px;box-sizing: border-box;overflow-y: auto;}#preview {width: 40%;padding: 20px;box-sizing: border-box;overflow-y: auto;background-color: #1e1e1e;border-left: 1px solid #444;}#fileTree {list-style-type: none;padding: 0;}#fileTree li {margin-bottom: 5px;cursor: pointer;user-select: none; /* 禁止文本选中 */}#fileTree .folder::before {content: "📂";margin-right: 5px;}#fileTree .file::before {content: "📄";margin-right: 5px;}#fileContent {white-space: pre-wrap; /* Preserve whitespace */background-color: #1e1e1e;padding: 10px;border: 1px solid #444;min-height: 200px;color: #f1f1f1;}#editArea {width: 100%;height: calc(100% - 40px);background-color: #1e1e1e;color: #f1f1f1;border: 1px solid #444;padding: 10px;box-sizing: border-box;}#saveButton {margin-top: 10px;background-color: #4caf50;color: white;border: none;padding: 10px 15px;cursor: pointer;border-radius: 5px;}#saveButton:hover {background-color: #45a049;}h1 {font-size: 1.2em;margin-bottom: 10px;}::-webkit-scrollbar {width: 8px;}::-webkit-scrollbar-track {background: #333;}::-webkit-scrollbar-thumb {background-color: #555;border-radius: 10px;border: 2px solid #333;}.hidden {display: none;}</style>
</head>
<body><div id="sidebar"><h1>选择目录</h1><button id="selectDirectoryButton">选择目录</button><ul id="fileTree"></ul></div><div id="content"><h1>编辑文件</h1><textarea id="editArea"></textarea><button id="saveButton">保存编辑后的文件内容</button></div><div id="preview"><h1>本地文件修改后的实时预览</h1><pre><code id="fileContent" class="plaintext"></code></pre></div><script>let currentFileHandle = null;document.getElementById('selectDirectoryButton').addEventListener('click', async function() {try {const directoryHandle = await window.showDirectoryPicker();const fileTree = document.getElementById('fileTree');fileTree.innerHTML = ''; // 清空文件树async function buildFileTree(directoryHandle, parentElement) {for await (const [name, entryHandle] of directoryHandle.entries()) {const li = document.createElement('li');li.textContent = name;if (entryHandle.kind === 'file') {li.classList.add('file');li.addEventListener('click', async function() {currentFileHandle = entryHandle;const file = await entryHandle.getFile();const fileContent = await file.text();const fileExtension = name.split('.').pop();const codeElement = document.getElementById('fileContent');const editArea = document.getElementById('editArea');codeElement.textContent = fileContent;editArea.value = fileContent;codeElement.className = ''; // 清除之前的语言类codeElement.classList.add(getHighlightLanguage(fileExtension));hljs.highlightElement(codeElement);// 显示编辑区域和保存按钮editArea.style.display = 'block';document.getElementById('saveButton').style.display = 'block';});} else if (entryHandle.kind === 'directory') {li.classList.add('folder');const ul = document.createElement('ul');ul.classList.add('hidden'); // 默认隐藏子目录li.appendChild(ul);li.addEventListener('click', function(event) {event.stopPropagation(); // 阻止事件冒泡ul.classList.toggle('hidden');});await buildFileTree(entryHandle, ul);}parentElement.appendChild(li);}}await buildFileTree(directoryHandle, fileTree);} catch (error) {console.log('Error: ', error);}});// 获取代码高亮语言类型function getHighlightLanguage(extension) {switch (extension) {case 'js': return 'javascript';case 'html': return 'html';case 'css': return 'css';case 'json': return 'json';case 'xml': return 'xml';case 'py': return 'python';case 'java': return 'java';default: return 'plaintext';}}// 保存编辑后的文件内容document.getElementById('saveButton').addEventListener('click', async function() {if (currentFileHandle) {const editArea = document.getElementById('editArea');const updatedContent = editArea.value;// 创建一个 writable 流并写入编辑后的文件内容const writable = await currentFileHandle.createWritable();await writable.write(updatedContent);await writable.close();// 更新高亮显示区域的内容const codeElement = document.getElementById('fileContent');codeElement.textContent = updatedContent;hljs.highlightElement(codeElement);}});// 初始化 highlight.jshljs.initHighlightingOnLoad();</script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/32466.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW在脑机接口(BCI)研究中的应用

脑机接口&#xff08;Brain-Computer Interface&#xff0c;BCI&#xff09;技术通过解读大脑活动&#xff0c;将人类思维与计算机或其他设备连接起来&#xff0c;广泛应用于神经康复、认知研究和人机交互等领域。LabVIEW作为强大的图形化编程环境&#xff0c;在BCI研究中发挥着…

数据结构十三:2 - 3树和红黑树

一开始就接触这五点&#xff0c;会让人云里雾里&#xff0c;不利于了解这个数据结构。因为这种先给定义在推导的方式并不适合学习。它没有介绍红黑树的来源&#xff0c;而只是给你生硬的定义。 而学习红黑树的最好学习资料就是大名鼎鼎的《算法4》&#xff0c;如下&#xff1a…

【Android源码解析】一篇搞定“路由、网络层、UI层、通信层

资料获取 扫一扫下方二维码即可免费领取1880页的《Android百大框架源码解析》 《Android 百大框架源码解析》 1.Retrofit 2.0源码解析 2.Okhttp3源码解析 3.ButterKnife源码解析 4.MPAndroidChart 源码解析 5.Glide源码解析 6.Leakcanary 源码解析 7.Universal-lmage-Loa…

【必看】每个开发人员都应该知道的 10 个 GitHub 库

GitHub&#x1f31f;&#xff1a;155K 被难题困住了&#xff1f;还是需要一些建议来指导你进入开发者行业&#xff1f;这个 仓库 将为你提供帮助。它拥有想要成为前端、后端或 DevOps 工程师需要的所有技术。你可以选择符合需求的或适合自己的&#xff0c;因为它提供了多种多…

【Android】【Java】【每日练手3】Android的四个主要组件使用示例

Android的四个主要组件是活动&#xff08;Activity&#xff09;、服务&#xff08;Service&#xff09;、广播接收器&#xff08;Broadcast Receiver&#xff09;和内容提供器&#xff08;Content Provider&#xff09;。下面通过一个简单的示例来介绍这四个组件及其用法。 示…

数据结构历年考研真题对应知识点(栈)

目录 3.1栈 3.1.1栈的基本概念 【栈的特点&#xff08;2017&#xff09;】 【入栈序列和出栈序列之间的关系(2022)】 【特定条件下的出栈序列分析(2010、2011、2013、2018、2020)】 3.1.2栈的顺序存储结构 【出/入栈操作的模拟(2009)】 3.1栈 3.1.1栈的基本概念 【栈…

YOLOv10目标检测算法的使用

目录 一、环境安装 1、创建虚拟环境 2、安装依赖 二、数据集准备 1、预训练权重 2、数据划分 3、建立数据集的yaml文件 三、训练 1、终端运行指令 2、建立一个 python 文件运行 四、验证 1、终端运行指令 2、建立一个 python 文件运行 五、模型推理 1、单张图片推…

Android开发实用必备的几款插件,提高你的开发速度

1.GsonFormat 使用方法&#xff1a;快捷键AltS也可以使用AltInsert选择GsonFormat&#xff0c;作用&#xff1a;速将json字符串转换成一个Java Bean&#xff0c;免去我们根据json字符串手写对应Java Bean的过程。 2.ButterKnife Zelezny 又叫黄油刀 使用方法&#xff1a;CtrlS…

JSON Web Tokens(JWT)与SpringSecurity如何配合使用?

JSON Web Tokens&#xff08;JWT&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;用于在网络应用环境间传递声明&#xff08;claims&#xff09;。JWT通常用于身份验证和信息交换&#xff0c;因为它们可以通过数字签名来验证。在Spring Security中&…

【Nodejs 日志库 】

总结了几个比较好用的Nodejs日志库&#xff0c;我认为一个 合格的日志库 需要 支持多种传输&#xff0c;如文件、控制台、HTTP 等。可定制的日志级别和格式。异步日志记录。 根据上述的需求&#xff0c;挑选出 几款比较好用的日志库&#xff0c; 1. Winston&#xff08;Gith…

AI学习指南机器学习篇-伯努利朴素贝叶斯算法简介

AI学习指南机器学习篇-伯努利朴素贝叶斯算法简介 1. 伯努利朴素贝叶斯算法的原理 1.1 算法的基本思想 伯努利朴素贝叶斯算法是基于贝叶斯定理和特征条件独立假设的分类算法。其基本思想是通过先验概率和类条件概率来计算后验概率&#xff0c;从而实现对样本进行分类。 1.2 …

直流电机三级串电阻启动

直流电动机在工农业生产中拥有广泛的应用&#xff0c;这主要得益于其调速范围广、调速平稳、过载能力强以及启动和制动转矩大的优点。为了降低起动电流和起动转矩&#xff0c;研究者们探索了直流电动机串电阻起动方法。这种方法通过在直流电动机电枢绕组中串入电阻&#xff0c;…

LeetCode:72. 编辑距离(Java DP)

目录 72. 编辑距离 题目描述&#xff1a; 实现代码与解析&#xff1a; 动态规划 原理思路&#xff1a; 72. 编辑距离 题目描述&#xff1a; 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操…

redis-5.0.5 利用redis-cli搭建集群,并扩缩容分片

背景&#xff1a;安装redis-5.0.5&#xff0c;使用redis-cli搭建redis集群&#xff0c;并扩缩分片。 安装redis-5.0.5 1、官网下载redis-5.0.5.tar.gz&#xff0c;并上传到服务器上。 2、我这里将redis-5.0.5.tar.gz上传至/usr/local/src目录下。 [rootlocalhost src]# ll /…

192.回溯算法:电话号码的字母组合(力扣)

代码解决 class Solution { public:// 定义每个数字对应的字母映射const string letterMap[10] {"", // 0"", // 1"abc", // 2"def", // 3"ghi", // 4"jkl", // 5"mno", // 6"pqrs&…

vscode+picgo+gitee实现Markdown图床

vscode中编辑Markdown文件&#xff0c;复制的图片默认是保存在本地的。当文档上传csdn时&#xff0c;会提示图片无法识别 可以在gitee上创建图床仓库&#xff0c;使用picgo工具上传图片&#xff0c;在Markdown中插入gitee链接的方式来解决该问题。 一、 安装picgo工具 1.1 v…

绿联nas折腾过程中遇到的问题

绿联nas折腾过程中遇到的问题 目录 ssh权限问题超级用户 ssh 权限问题 使用chmod -R 777 目录/ 给指定目录及其所有子目录和文件设置最大的权限&#xff0c;权限设置为 rwxrwxrwx&#xff08;读、写、执行权限给所有用户&#xff09;。这个命令会将目录和文件的权限设置为非…

Kimichat使用案例027:有效使用 kimichat 的15个高级技巧

文章目录 一、明确具体:表达清晰、避免使用模糊措辞。二、提供背景信息:提供相关的细节和背景信息。三、每次只问一个问题四、设定明确的标准五、要求解释六、管理期望七、确定问题类型八、调整语言水平九、提供范例十、及时提供反馈十一、明确对话角色十二、 保持对话的连贯…

Mysql之GROUP BY与PARTITION BY区别

GROUP BY GROUP BY 是一个SQL子句&#xff0c;用于将结果集按一个或多个列进行分组&#xff0c;然后对每个组应用聚合函数&#xff08;如 SUM, COUNT, AVG 等&#xff09;。它会改变查询的结果集&#xff0c;使其只包含每个组的汇总信息。 例如&#xff1a; sql SELECT empl…

Java多线程编程与并发控制策略

Java多线程编程与并发控制策略 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;我想和大家分享一下Java多线程编程与并发控制策略的相关知识&am…