electron在单例中实现双击打开文件,并重复打开其他文件

单实例的思路

  1. 首次通过双击文件打开应用
    • filePath传给render
  2. 使用中的应用,再次双击打开文件
    • 第一个实例创建时,同时创建一个通信服务器net.createServer()
    • 第二个实例创建时,连接第一个服务器net.createConnection()
    • 将再次打开的filePath传递给第一个实例
    • 然后在传递给render

1. 首次通过双击文件打开应用

在主进程展示的时候传递filePath

ts 复制代码
  mainWindow.on('ready-to-show', () => {
    //隐藏启动页
    if (loadingWindow && !loadingWindow?.isDestroyed()) {
      loadingWindow?.hide()
      loadingWindow?.removeAllListeners()
      loadingWindow?.destroy()
    }
    mainWindow.show()
    /**
     * @description 双击打开本地文件
     */
    openFileFromDoubleClick(mainWindow)
  })

获取filePath并传递给render

ts 复制代码
export function openFileFromDoubleClick(mainWindow) {
  if (process.argv.length >= 2) {
    const argv = process.argv.slice(app.isPackaged ? 1 : 2)
    const filePath =
      argv.find((arg) => arg.endsWith('.krzj')) ||
      argv.find((arg) => arg.includes('--file'))?.split('=')[1]
    if (filePath && filePath.endsWith('.krzj')) {
      // 当页面加载完成后,获取到vue-ready事件后,发送open-file事件
      ipcMain.once('vue-ready', () => {
        mainWindow.webContents.send('open-file', filePath)
      })
    }
  }
}

2. 注册preload事件

ts 复制代码
 //双击打开文件
 onOpenFile: (callback: any) => ipcRenderer.on('open-file', callback),
 //消息传递
 send: (channel, data) => ipcRenderer.send(channel, data),

3. render接收信息

需要先通知主进程render加载完毕,才从主进程拿filePath,否则获取不到

ts 复制代码
onMounted(() => {
  // 在health接口返回后 获取双击打开的文件路径
  window.api.send('vue-ready')
  window.api.onOpenFile((event: any, path: string) => {
    if (path && route.path === '/file') {
      // 在当前页直接获取跳转
      openProjectFile(path)
    } else if (path && route.path !== '/file') {
      // 在非当前页则回来后获取跳转
      router.push('/file')
      openProjectFile(path)
    }
  })
})

4. 主进程创建通信服务器

ts 复制代码
// 锁定应用只能单列运行
const appSingleInstance = app.requestSingleInstanceLock()
if (!appSingleInstance) {
  // 第二个实例 - 连接第一个实例的服务器
  sendFilePathToFisrtInstance(PORT)
  app.quit()
} else {
  // 第一个实例 - 创建服务器 获取第二个实例发送的filepath 封装后不能再发送
  server = net.createServer((socket) => {
    socket.on('data', (data) => {
      mainWindow?.webContents.send('open-file', data.toString())
    })
  })
  server.listen(PORT)
  server.on('error', (err) => console.error('服务器错误:', err))
}

5. 第二个实例连接服务器

ts 复制代码
/**
 * @description 第二个实例 - 连接第一个实例的服务器
 * @export
 */
export function sendFilePathToFisrtInstance(port: number) {
  const argv = process.argv.slice(app.isPackaged ? 1 : 2)
  const filePath =
    argv.find((arg) => arg.endsWith('.krzj')) ||
    argv.find((arg) => arg.includes('--file'))?.split('=')[1]
  if (filePath) {
    const client = net.createConnection({ port: port }, () => {
      client.write(filePath)
      client.end()
    })
    client.on('error', () => {})
  }
}

开发时如何本地测试打开多个文件

使用的是electron-vite,在package.json创建运行脚本,一条就是打开一个文件,可以开多个终端打开多个文件

ts 复制代码
 "open-file": "electron-vite dev -- --file \"D:/kr/untitled01.krzj\"",
 "open-file1": "electron-vite dev -- --file \"D:/kr/untitled02.krzj\"",
 "open-file2": "electron-vite dev -- --file \"D:/kr/untitled03.krzj\""

windows如何关联自定义文件关联启动

我是用的是electron-builder,然后在electron-builder.yml中配置就行,非常简单

yml 复制代码
# 设置自定义文件关联启动
fileAssociations:
  description: kingrayFile
  # 自定义文件后缀
  ext: krzj
  # 自定义文件图标
  icon: build/icons/win/icon.ico