Electron文件处理的那些事

本文为Electron+Vue3开发桌面端软件系列文章第四篇

通过本文,你将获得

  1. Electron中进程间通信
  2. chokidar处理文件实时更新

前言

对于今日清单这款软件,体量小需求简单。我在设计之初,想要的就是本地化使用的场景,因此在技术实现上不会涉及到服务端、数据库,那又想要存储每日清单数据,只能采用本地文件存储的方案。

简单罗列一下需求:

  1. 当日创建首个待办项时创建文件,文件以当天日期为名
  2. 需要一个文件目录栏,读取日志文件夹
  3. 切换每日待办,读取相应文件内容进行展示
  4. 本地资源管理器中新增或删除文件,目录栏中可以实时更新

在Electron中文件处理都是依赖进程间通信 (IPC) ,可以先在官网中阅读这部分内容,了解一下相关的背景知识。

IPC:www.electronjs.org/zh/docs/lat...

创建今日待办

首次创建时,分成两部分新增逻辑。一个是当前界面中待办列表的数据新增,也就是操作list,这里的list就是展示的待办列表项数组;另一个是在指定文件夹下创建当日待办的文件,再将list数据写入。

每项待办项的数据格式可自行定义,这取决于想在页面中显示什么内容。

新增 list

lua 复制代码
list.value.push({
  id: Math.random() * 100, // Unique key
  name: '', // todo item
  time: dayjs().format('MM-DD HH:mm:ss'), // create time
  isEdit: true
})

那紧接着就是将新增的list数据写入文件,

javascript 复制代码
function handleExport() {
  const content = list.value
    .filter((item) => !item.isEdit)
    .map((item, index) => {
      const timeArr = [item.time.split(' ')[1], item.notify].filter((item) => item)
      return `${item.isFinish ? '√' : '×'} ${index + 1}.${item.name} ->${timeArr}\n`
    })

  const time = fileTime.value || dayjs().format('YYYY-MM-DD')
  window.electronFile.exportFile({
    path: fileUrl.value,
    time,
    content: `${time}待办事项\n${content.join('')}`
  })

  emits('exportFile')
}

handleExport方法中content是对list原始数据处理,处理成需要在本地文件中的展现形式,这就相当于两种数据格式的转换。

转换规则:

这里更关键的是electronFile.exportFile,挂载在window上的方法,就是通过Eelectron进程间通信实现的。

需要在preload/index.js预加载脚本中设置,使用 ipcRenderer.send API 发送消息,

javascript 复制代码
contextBridge.exposeInMainWorld('electronFile', {
  exportFile: (data) => ipcRenderer.send('exportFile', data)
})

在主进程中使用 ipcMain.on API 接收,

javascript 复制代码
ipcMain.on('exportFile', (_, data) => {
  writeFile(data)
})

接收到Vue应用也就是页面上传递过来的数据,刚刚在handleExport方法中处理过的数据,

javascript 复制代码
{
  path: fileUrl.value,
  time,
  content: `${time}待办事项\n${content.join('')}`
}

writeFile写入文件,使用node实现,

javascript 复制代码
import fs from 'node:fs'
import { join } from 'node:path'

export function writeFile({ path, time, content }) {
  const filePath = join(path, `${time}.txt`)
  fs.writeFileSync(filePath, content, function (err) {
    if (err) {
      console.log('err', err)
    } else {
      console.log('file success')
    }
  })
}

文件目录栏

需要展示文件目录栏,需要在指定目录下读取所有的文件进行展示。

原本的实现方案就是简单的使用nodefs模块读取文件,但是考虑到后续的一些需求,需要实时监听目录下文件变化,原生的fs模块或者是fs.watchfs.watchFile稍显不足。

这里推荐使用一个库chokidar,查看 www.npmjs.com/package/cho... 进行了解。

依旧是使用进程间通信 (IPC)来实现,在Vue应用APP.vue中,

javascript 复制代码
window.electronFile.readFileNames(url)

这里的url是提前设定好的待办日志存放目录地址的路径。

preload/index.js预加载脚本中设置readFileNames方法,

javascript 复制代码
contextBridge.exposeInMainWorld('electronFile', {
  readFileNames: (data) => ipcRenderer.send('readFileNames', data)
})

主进程main/index.js中监听readFileNames

javascript 复制代码
import { normalize } from 'node:path'
import chokidar from 'chokidar'

ipcMain.on('readFileNames', (fileEvent, path) => {
  chokidar
    .watch(path, {
      persistent: true // 持续监听
    })
    .on('all', (event, path) => {
      fileEvent.sender.send('directoryChanges', { event, path: normalize(path) })
    })
})

上面代码中,在监听到指定目录下有变化时,fileEvent.sender.send会发出一个消息事件directoryChanges,将获取的文件数据暴露出来。

那在App.vue这端的Vue应用中可以通过ipcRenderer来接收主进程回复的消息,并进行数据格式上的处理,就可以把文件中数据回显到页面文件目录栏里。

csharp 复制代码
window.electron.ipcRenderer.on('directoryChanges', (_, data) => {
  const { event, path } = data
  const name = path.split('\')[path.split('\').length - 1]
  const unit = name.split('.')[1]

  const code = dayjs(name.split('.')[0]).valueOf()
  const index = fileNames.value.findIndex((item) => item.code === code)
  if (event == 'add' && index === -1 && unit === 'txt') {
    fileNames.value.unshift({
      name,
      code
    })
  }
  if (event == 'unlink' && index !== -1) {
    fileNames.value.splice(index, 1)
  }
  fileNames.value.sort((a, b) => b.code - a.code)
})

那上述的实现方法都封装在一个函数getFileNames里,那何时去触发这个函数呢?换言之,什么时候监听到文件夹下有文件变化?来更新目录栏数据呢。

其实在readFileNames方法里是需要指定文件夹路径url参数的,可以监听这个url,在watch中执行,同时会读取文件内容getFileContent

scss 复制代码
watch(
  fileUrl,
  (url) => {
    if (url) {
      fileNames.value = []
      getFileNames(url)
      getFileContent()
    }
  },
  { immediate: true }
)

总结

Electron应用相当一个套壳的Vue应用(也可以是React,原生,或者其他...),它解决了Vue应用实现客户端的问题,例如可以依托node对于本地文件处理。在 Electron 中,进程使用 ipcMainipcRenderer 模块,通过定义的通道传递消息来进行通信。那这过程中也不能直接通信,需要使用预加载脚本在上下文隔离渲染器进程中导入 Node.js 和 Electron 模块。

单向传递消息 ,渲染器进程到主进程:使用 ipcRenderer.send 发送消息,然后使用 ipcMain.on 接收。

例如:获取所有文件名称

双向传递消息 ,从渲染器进程代码调用主进程模块并等待结果,这可以通过将 ipcRenderer.invokeipcMain.handle 搭配使用来完成。

例如:打开指定文件夹

相关推荐
jacy1 分钟前
图片大图预览就该这样做
前端
林太白3 分钟前
Nuxt3 功能篇
前端·javascript·后端
YuJie4 分钟前
webSocket Manager
前端·javascript
Mapmost19 分钟前
Mapmost SDK for UE5 内核升级,三维场景渲染效果飙升!
前端
Mapmost22 分钟前
重磅升级丨Mapmost全面兼容3DTiles 1.1,3DGS量测精度跃升至亚米级!
前端·vue.js·three.js
wycode28 分钟前
Promise(一)极简版demo
前端·javascript
浮幻云月29 分钟前
一个自开自用的Ai提效VsCode插件
前端·javascript
DevSecOps选型指南31 分钟前
SBOM风险预警 | NPM前端框架 javaxscript 遭受投毒窃取浏览器cookie
前端·人工智能·前端框架·npm·软件供应链安全厂商·软件供应链安全工具
__lll_39 分钟前
Docker 从入门到实战:容器、镜像与 Compose 全攻略
前端·docker