使用electron实现收藏夹功能Bug整理

339 阅读4分钟

前言

本文主要是整理,在用electron实现收藏夹功能时解决了的Bug!

Bug

问题1:

main.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules.

意思: main.js 被视为 ES 模块文件,因为它是一个 .js 文件,其最近的父 package.json 包含 "type": "module" ,它将该包范围内的所有 .js 文件声明为 ES 模块。

解决方案 将 package.json 中的"type": "module" 删除

问题2:

winState is not a constructor

出现原因

const winState=require("electron-win-state")

使用require引入electron-win-state时,引入的是一个 这样的 { default: \[class WinState] } 对象,所以需要将

 const winState=require("electron-win-state").default

这样便解决了这个问题了。

需要注意的是,使用import引入不会出现这样的问题,所以在使用import引入时不用加default!

问题3:**expected "indent", got "outdent" **

使用styluscss 样式报错: expected "indent", got "outdent"

问题:

  1. tab键和空格键混用
  2. 缩进不正确,父子级和兄弟级混淆;样式没缩进,缩进错误
  3. 代码没有写完整,存在残缺代码,或者代码编写错误

解决方案: 可以设置文件保存时,自动格式化,这样修复我们的空格和tab键使用错误的问题。

问题4:处理默认样式问题

处理默认样式问题,安装normalize.css插件,并在src下的main文件中引入即可。

import 'normalize.css'

问题4:

主进程与渲染进程之间通信乱码问题。

问题5VM21 sandbox_bundle:2 Unable to load preload script: D:\study\electron-egg\favorites\readit\preload\index.js

主进程加载预加载文件报错 遇到这个问题:

  1. 检查路径是否正确
  2. 检查预加载文件是否存在错误

问题6:electron 解除安全警告

两种方法:

  1. 在src/renderer/main.js中,任意一行代码加入: process.env\['ELECTRON\_DISABLE\_SECURITY\_WARNINGS'] = 'true'
  2. <meta http-equiv="Content-Security-Policy" content="default-src 'self'">,将 Content-Security-Policy 头设置为 'default-src 'self'',表示只允许从同一域名加载资源。

问题7: winState.manage is not a function

出现Bug原因:直接使用了引入的默认类调用manage方法,应该使用实例对象调用manage。 npm 文档有坑:

image.png 文档中实例名字直接等于引入类的名字,需要特别注意一下!

问题8:使用electron-win-state进行不同窗口之间状态管理,出现不同窗口之间窗台互相干扰问题。

例如: 调整子窗口大小,关闭子窗口,再关闭主窗口,再打开主窗口,主窗口大小变为了我们设置的子窗口的样式

解决方案: 给每个窗口保存状态时,设置一个单独的名字,用到electronStoreOptions属性。

const winstate = new windowStateKeeper({
        defaultWidth: 1200,
        defaultHeight: 800,
        dev: true,
        // 解决窗口之间相互干扰问题
        electronStoreOptions: {
            name: 'window-state-open'
        }
    });

问题9:Uncaught SyntaxError: Invalid or unexpected token

const cssText = `top: 50px;
left: 50px;
position:fixed;
`
let code = `const div =document.createElement('div')
div.id = 'readit-button'
div.innerHTML = '关闭窗口'
div.addEventListener('click',()=>{myApi.close()})
div.style.cssText = '${cssText}'
document.body.appendChild(div)`

上面这段代码报这样的错误,出现原因:cssText中的样式不允许换行。

问题10:executeJavaScript向子窗口注入js代码报错:

An object could not be cloned.at t.ipcRendererInternal.send

解决方案:executeJavaScript返回promise对象,使用catch捕获错误,并进行处理。

问题11 在打开的新窗口中注入js代码,并实现与主进程通信。

  1. 通过 contents.executeJavaScript(code[, userGesture])向一个子窗口中注入js代码
  2. 通信:
  • 主进程只有一个,渲染进程可以有很多个
  • 每个渲染进程与主进程通信,都需要有一个预加载文件。文件中定义渲染进程触发的方法。
  • 子窗口打开的页面上调用方法.

新的子窗口对应的预加载文件:

const { contextBridge, ipcRenderer, } = require('electron');
const closeWindow = () => {
    ipcRenderer.invoke('close-window-event');
}

contextBridge.exposeInMainWorld('myWindowApi', {
    closeWindow
})

打开子窗口时,设置预加载文件。

win = new BrowserWindow({
        ...winstate.winOptions,
        webPreferences: {
            preload: path.join(__dirname, '../preload/open.js'), // 引入预处理文件
        },
    });

定义主进程中接受方法:

//关闭窗口事件
ipcMain.handle('close-window-event', async (e) => {
    win.close();
})

页面上调用:

const cssText = `bottom: 50px;right: 50px;position:fixed;z-index:1000;width:100px;height:30px;background-color: cornflowerblue;border-radius: 5px;text-align :center;line-height:30px;cursor:pointer;color:#fff;`
let code = `const div =document.createElement('div')
div.id = 'readit-button'
div.innerHTML = '关闭窗口'
div.addEventListener('click',()=>{myWindowApi.closeWindow()})
div.style.cssText = '${cssText}'
document.body.appendChild(div)`

electron 主线程向渲染线程发送消息

  1. 首先渲染进程和主进程之间建立一个通信通道
  • 渲染进程向主进程发送请求
  • 主进程收到请求,将event暂存起来。
  1. 在主进程对应的事件处理完毕之后,调用event.sender.send('channel',data)方法向渲染进程返回数据
  2. 渲染进程使用ipcRenderer.on('channel',()=>{})接收主进程返回的数据

例如:主进程保存图片成功之后要通知渲染进程更新图片列表

渲染进程的预加载文件

const getFileListOnMain = (cb) => {
    ipcRenderer.send('on-filelist-event');// 先建立与主进程的通信通道
    // 主进程向渲染进程发送消息时接收的事件
    ipcRenderer.on('message-from-main', (e, data) => {
        cb()
    });
}

contextBridge.exposeInMainWorld('myApi', {
    getFileListOnMain
})

主进程文件

const { Menu, dialog, ipcMain } = require("electron"); //Menu创建原生菜单喝上下文菜单,dialog 模块提供了api来展示原生的系统对话框
const path = require('path');
//避免文件名重复,引入第三方库设置文件名
const randomstring = require("randomstring");
//保存时需要知道图片类型,需要引入第三方库
const imageType = require('image-type')

//需要根据url得到文件内容,使用got
const got = require("got").default;

//引入node文件模块
const { writeFile } = require('fs')

const { readdir } = require('fs/promises') //node读取一个目录下所有的文件和子目录

//建立与渲染进程的通信通道
let event = null
ipcMain.on('on-filelist-event', (e, args) => {
    event = e;
    // 由于主进程要做别的异步操作之后才向渲染进程发送消息,
    //所以先将对应的event暂存起来,等到对应的异步事件处理完毕之后再返回数据给渲染进程
})

const save = (srcURL) => {


    const template = [{
        label: '图片另存为.....',
        click: async () => {
            //根据url得到文件内容
            let content = await got.get(srcURL);
            //将文件内容转成二进制
            let bufferContent = Buffer.from(content.rawBody)

            // 创建文件名字随机数
            let imgNameRandom = randomstring.generate(10);
            //判断图片类型
            let { ext } = imageType(bufferContent)

            //组装文件类型
            let fileName = imgNameRandom + '.' + ext
            const { canceled, filePath } = await dialog.showSaveDialog({
                title: '图片另存为',
                defaultPath: path.join(__dirname, `../public/upload/${fileName}`)
            })

            // 如果被保存了,将文件内容写入
            if (!canceled) {
                writeFile(filePath, bufferContent, async () => {
                    event.sender.send('message-from-main', 'change') // 图片保存完毕之后,通知渲染进程
                });
            }
        }
    }]
    const menu = Menu.buildFromTemplate(template);
    menu.popup();
}

module.exports = save