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 搭配使用来完成。

例如:打开指定文件夹

相关推荐
正小安1 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch3 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光3 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   3 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   3 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web3 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常3 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇4 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr4 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho5 小时前
【TypeScript】知识点梳理(三)
前端·typescript