airi 项目实战基于 VueUse useFileSystemAccess 在浏览器中创建、读取与写入本地文件【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi导读useFileSystemAccess是 VueUse 提供的浏览器端Browser 分类组合式函数它将现代浏览器原生的 File System Access API 封装为声明式的 Vue 响应式状态与操作函数让开发者可以在 Web 应用中打开、创建、读取、写入用户本地文件。在 airi 这类同时拥有 Web、桌面端Electron与移动端Capacitor多端形态的 AI 陪伴应用中该能力可以用于本地化地读取配置、导入模型素材、导出对话记录等场景。阅读本文后你将掌握useFileSystemAccess的完整 API 用法、dataType数据形态选择、open / create / save / saveAs / updateData五个操作的调用方式并通过 airi 仓库中同源的 File System Access / OPFS 缓存实现理解其底层FileSystemFileHandle与FileSystemWritableFileStream的真实工作方式。一、为什么需要 useFileSystemAccess传统 Web 应用读写本地文件时通常只能依赖input typefile选择文件只读或借助a[download]触发下载无法覆盖原文件。File System Access API 打破了这一限制通过window.showOpenFilePicker与window.showSaveFilePicker页面可以在用户授权下获得真实文件的句柄File Handle从而原地读取、改写用户本地文件体验接近原生桌面应用。但原生 API 使用起来较为繁琐需要手动处理浏览器能力检测、句柄获取、文件读取、可写流Writable Stream创建与关闭、文件元信息提取等环节。VueUse 的useFileSystemAccess把这一切收敛为一个组合式函数并在 Vue 的响应式系统中以ref/computed形式暴露文件内容与元数据天然适配 airi 基于 Vue 3 Vite UnoCSS 的前端架构本技能来源参见 .agents/skills/vueuse-functions/SKILL.md 中 Browser 分类的AUTO调用规则适用时自动使用。二、快速上手基础用法在 Vue 3或 Nuxt 3项目中引入import { useFileSystemAccess } from vueuse/core const { isSupported, data, file, fileName, fileMIME, fileSize, fileLastModified, create, open, save, saveAs, updateData } useFileSystemAccess()调用open()会弹出系统文件选择框用户选中的文件内容会写入data同时fileName、fileMIME、fileSize、fileLastModified等元数据自动同步更新调用create()/save()/saveAs()则走保存流程。整个使用过程中无需手动维护任何事件监听或文件句柄状态——这正是组合式函数相对原生 API 的核心价值。返回值速查返回值类型说明isSupportedRefboolean当前浏览器环境是否支持 File System Access API常用于渲染降级 UI 或禁用按钮dataShallowRefT \| undefined文件内容数据具体类型取决于dataType选项string/ArrayBuffer/BlobfileShallowRefFile \| undefined当前打开的File对象本身fileNameComputedRefstring当前文件名不含路径出于安全考虑 API 不暴露完整路径fileMIMEComputedRefstring文件的 MIME 类型fileSizeComputedRefnumber文件大小字节fileLastModifiedComputedRefnumber文件最后修改时间戳create函数弹出保存对话框创建新文件返回后可通过save写入open函数弹出打开对话框选择本地文件并读取save函数将data写入已持有的文件句柄原地覆盖saveAs函数弹出保存对话框另存为获取新的文件句柄updateData函数从当前file句柄重新读取内容并刷新data三、选项与类型声明详解useFileSystemAccess提供了一套完整的 TypeScript 类型声明完整定义见 .agents/skills/vueuse-functions/references/useFileSystemAccess.md下面逐项拆解其语义。3.1 dataType决定 data 的数据形态export type UseFileSystemAccessOptions ConfigurableWindow UseFileSystemAccessCommonOptions { /** * file data type */ dataType?: MaybeRefOrGetterText | ArrayBuffer | Blob }dataType是核心选项支持Text默认字符串、ArrayBuffer二进制原始字节与Blob二进制大对象三种取值且可以是ref或 getterMaybeRefOrGetter即可以运行时动态切换。该选项直接决定data的类型并通过函数重载在编译期给出精确的类型推导export declare function useFileSystemAccess(): UseFileSystemAccessReturn string | ArrayBuffer | Blob export declare function useFileSystemAccess( options: UseFileSystemAccessOptions { dataType: Text }, ): UseFileSystemAccessReturnstring export declare function useFileSystemAccess( options: UseFileSystemAccessOptions { dataType: ArrayBuffer }, ): UseFileSystemAccessReturnArrayBuffer export declare function useFileSystemAccess( options: UseFileSystemAccessOptions { dataType: Blob }, ): UseFileSystemAccessReturnBlob三种形态的选型建议Text适合.json、.md、.txt、.yaml等文本类文件。airi 中涉及角色卡、对话记录、配置导出的场景可直接用字符串处理ArrayBuffer适合需要按字节精确处理的二进制场景例如分块读取、哈希计算、加密Blob适合图片、音频、模型文件等大体积二进制资源可直接用于URL.createObjectURL、FormData上传或FileReader后续处理。3.2 打开文件选项showOpenFilePicker 参数export interface FileSystemAccessShowOpenFileOptions { multiple?: boolean types?: Array{ description?: string accept: Recordstring, string[] } excludeAcceptAllOption?: boolean }multiple是否允许多选默认为falsetypes可接受的文件类型描述数组每个条目包含description下拉框显示文本与acceptMIME 类型到扩展名数组的映射例如{ description: 文本文件, accept: { text/plain: [.txt] } }excludeAcceptAllOption为true时在文件选择器中隐藏所有文件选项强制用户选择指定类型。3.3 保存文件选项showSaveFilePicker 参数export interface FileSystemAccessShowSaveFileOptions { suggestedName?: string types?: Array{ description?: string accept: Recordstring, string[] } excludeAcceptAllOption?: boolean }相比打开选项多出suggestedName建议文件名用于在保存对话框中预填文件名如对话记录.txt。3.4 抽取后的组合选项useFileSystemAccess从上述两个原生选项对象中抽取了自己所需的子集export type UseFileSystemAccessCommonOptions Pick FileSystemAccessShowOpenFileOptions, types | excludeAcceptAllOption export type UseFileSystemAccessShowSaveFileOptions Pick FileSystemAccessShowSaveFileOptions, suggestedName 即open方法只透传types与excludeAcceptAllOptioncreate、save、saveAs三个保存类方法只透传suggestedNamesave原地覆盖时通常无需传。四、完整实战示例本地文本编辑器将上述 API 组合起来可以非常简洁地实现一个带打开 / 保存 / 另存为 / 新建能力的本地文件编辑器script setup langts import { computed, ref } from vue import { useFileSystemAccess } from vueuse/core const { isSupported, data, fileName, fileMIME, fileSize, fileLastModified, create, open, save, saveAs, updateData, } useFileSystemAccess({ dataType: Text }) const editorContent ref() const lastSaved refnumber | null(null) // 将响应式 data 同步到编辑器可选用 watch 双向同步 async function handleOpen() { await open({ types: [{ description: 文本文件, accept: { text/plain: [.txt, .md] } }] }) if (data.value ! null) editorContent.value data.value } async function handleSave() { // 直接覆盖当前文件句柄若来自 open 则原地写回 await save() lastSaved.value Date.now() } async function handleSaveAs() { await saveAs({ suggestedName: fileName.value ?? untitled.txt }) lastSaved.value Date.now() } async function handleCreate() { await create({ suggestedName: 新文件.txt }) await save() } const metaSummary computed(() 文件${fileName.value ?? 未打开}类型${fileMIME.value ?? -}大小${fileSize.value ?? 0} 字节修改时间${fileLastModified.value ? new Date(fileLastModified.value).toLocaleString() : -}) /script template div v-ifisSupported button clickhandleOpen打开/button button clickhandleSave保存/button button clickhandleSaveAs另存为/button button clickhandleCreate新建/button button clickupdateData重新读取/button p{{ metaSummary }}/p textarea v-modeleditorContent rows12 cols80 / /div div v-else 当前浏览器不支持 File System Access API请使用较新的 Chrome / Edge。 /div /template要点说明文件打开后updateData()可用于重新读取磁盘上被外部修改后的内容save()对来自open()的文件句柄执行原地覆盖这是传统input typefile方案做不到的能力create()与saveAs()都会先弹出系统保存对话框区别在于create语义上用于新建。五、底层原理句柄与可写流useFileSystemAccess的类型声明同时揭示了其底层依赖的浏览器对象结构理解它们有助于排查问题与扩展能力5.1 FileSystemFileHandle文件句柄export interface FileSystemFileHandle { getFile: () PromiseFile createWritable: () FileSystemWritableFileStream }getFile()返回该文件对应的File对象含 name、type、size、lastModified 属性用于读取createWritable()创建可写流用于写入内容。5.2 FileSystemWritableFileStream可写流interface FileSystemWritableFileStream extends WritableStream { write: FileSystemWritableFileStreamWrite seek: (position: number) Promisevoid truncate: (size: number) Promisevoid }write是一个支持多种重载的联合签名interface FileSystemWritableFileStreamWrite { (data: string | BufferSource | Blob): Promisevoid (options: { type: write position: number data: string | BufferSource | Blob }): Promisevoid (options: { type: seek; position: number }): Promisevoid (options: { type: truncate; size: number }): Promisevoid }即write既可以整体写入一段数据字符串、二进制缓冲区或 Blob也可以结合seek移动写入位置与truncate截断到指定大小实现随机访问写入——例如只修改大文件的某一段而不必重写整个文件。5.3 浏览器窗口类型声明export type FileSystemAccessWindow Window { showSaveFilePicker: ( options: FileSystemAccessShowSaveFileOptions, ) PromiseFileSystemFileHandle showOpenFilePicker: ( options: FileSystemAccessShowOpenFileOptions, ) PromiseFileSystemFileHandle[] }useFileSystemAccess的isSupported正是基于对showOpenFilePicker/showSaveFilePicker是否存在进行能力检测这也是 VueUseuseSupported模式在 Browser 场景的典型应用因此不支持该 API 的浏览器如部分 Safari / Firefox 版本中isSupported为false业务代码应据此提供降级路径。六、仓库佐证airi 中同源的本地文件读写实践airi 仓库虽然没有直接调用useFileSystemAccess但多个核心包都依赖vueuse/core包括 apps/stage-web/package.json、packages/stage-ui/package.json、packages/stage-ui-live2d/package.json 等并且在 Live2D 与 MMD 模型加载链路中对 File System Access API 的底层句柄与可写流接口做了真实的生产级运用——这与useFileSystemAccess封装的底层 API 完全同源。6.1 Live2D 模型的 OPFS 缓存packages/stage-ui-live2d/src/utils/opfs-loader.tsopfs-loader.ts 中的OPFSCache类把已加载的 Live2D zip 模型持久化到浏览器 OPFSOrigin Private File System避免重复网络拉取写入writeFile通过getFileHandle(name, { create: true })获取句柄再createWritable()→writable.write(content)→writable.close()完成落盘与useFileSystemAccess内部写文件的流程一致读取readDirectoryRecursive通过dir.values()递归遍历目录、fileHandle.getFile()还原File对象采用__meta.json记录sourceUrl与 schemaversion当前为live2DOpfsCacheVersion 3版本不匹配或来源 URL 变化时自动清除旧缓存重建——与useFileSystemAccess场景中文件元信息随内容一同管理的思路一脉相承。6.2 MMD 源文件缓存packages/stage-ui-mmd/src/utils/opfs-loader.tsMMD 版 opfs-loader.ts 的writeFile采用了完全相同的三步写入模式getFileHandle→createWritable→write/close并明确指出OPFS 是可选的加速层缓存 miss 时回退到 fetch这正是 File System Access 类能力在真实产品中应具备的优雅降级态度。6.3 可写流接口的单元测试packages/stage-ui-live2d/src/utils/opfs-loader.test.tsopfs-loader.test.ts 用内存模拟实现了MemoryFileHandle.createWritable()返回{ write, close }完整验证了句柄 → 可写流 → 写入 → 关闭链路。这也从侧面说明FileSystemWritableFileStream的write接口同时接受string与Blob与 useFileSystemAccess.md 中声明的write(data: string | BufferSource | Blob)完全吻合。如果你需要在 airi 的 Web 端实现本地导入角色配置 / 导出对话记录 / 保存 Live2D 模型 zip等能力建议遵循两条路线面向用户交互的文件选择与编辑使用useFileSystemAccess声明式完成面向程序化的持久化缓存借鉴上述 OPFS 实现二者共享同一套 File System Access 底层协议。七、浏览器兼容性与降级策略File System Access API 目前主要在 Chromium 系浏览器Chrome、Edge中可用因此必须优先检查isSupported不支持时可按场景降级读取可用input typefileFileReader保存可用URL.createObjectURLa[download]但无法原地覆盖原文件页面运行环境要求安全上下文HTTPS 或 localhost非安全环境下相关 API 不可用涉及敏感文件操作时浏览器会在打开、保存时分别向用户请求授权且用户可随时撤销授权业务代码需对句柄失效如抛错做容错处理。八、小结useFileSystemAccess用一个组合式函数覆盖了浏览器本地文件读写的完整生命周期open读取、create/save/saveAs写入、updateData刷新、dataType控制数据形态、元数据以 computed 形式即时响应。结合 airi 仓库中 opfs-loader.ts 与 opfs-loader.test.ts 对底层句柄、可写流接口的生产级运用你可以放心地把该能力引入 Web 端功能开发让 AI 陪伴应用拥有读写用户本地文件的桌面级体验。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考