electron实战开发一个摸鱼神器,从入门到入土

577 阅读4分钟

前言

最初的本意是想着用electron快速开发一个阅(mo)读(yu)神器。哪知道在开发过程遇到了很多坑,朋友在使用过程中也提了很多需求和bug,新手第一次使用electron,写个笔记记录开发过程遇到的问题

一、开发的主要功能:

  1. 窗口随意拖动(此处有坑,后文介绍)
  2. 支持自定义背景透明度、文字颜色、字体大小和状态栏图标
  3. 支持本地导入txt、pdf和epub格式
  4. 支持线上书源采集
  5. 使用非对称加密实现激活码功能

二、主要功能展示:

窗口随意拖动 mofish_resize.gif

快速隐藏小说,“偷”感十足 mofish_hide.gif

支持本地txt、pdf和epub格式的小说导入 mofish_localImport.gif

支持线上实时小说采集 mofish_online.gif

支持透明度、文字颜色、字体大小和状态栏图标设置 image.png

三、遇到的问题:

1. electron拖动窗口失效的问题

由于主阅读窗口是设置了没有标题栏的窗口,又需要设置可一拖拽,一开始尝试设置了-webkit-app-region: drag;属性,然后问题就来了。设置了这个属性的元素所有的鼠标事件都没法响应了。网上找了一大圈,都没有啥好的方法。最后只能通过自定义鼠标事件来实现。经过测试Mac系统和windows系统中拖拽都比较丝滑,核心代码如下:

/**
   * 窗口移动事件
   */
  ipcMain.on("window-move-open", (events, canMoving) => {
    if (canMoving) {
      // 读取原位置
      const winPosition = win.getPosition();
      winStartPosition = { x: winPosition[0], y: winPosition[1] };
      mouseStartPosition = screen.getCursorScreenPoint();
      // 清除
      if (movingInterval) {
        clearInterval(movingInterval);
      }
      // 新开
      movingInterval = setInterval(() => {
        // 实时更新位置
        const cursorPosition = screen.getCursorScreenPoint();
        const x = winStartPosition.x + cursorPosition.x - mouseStartPosition.x;
        const y = winStartPosition.y + cursorPosition.y - mouseStartPosition.y;
        win.setPosition(x, y, true);
      }, 20);
    } else {
      clearInterval(movingInterval);
      movingInterval = null;
    }
  });

2. 电脑状态栏图标隐藏的问题

隐藏系统状态栏图标MacOs和windows调用的api是有缺别的。我使用的是electron31.0.2版本,不知道其他版本是否有这个区别

// 监听【设置】子窗口发送的消息
ipcMain.on('message-from-child', (event, message) => {
  console.log('Message from child:', message);
  if(process.platform == "darwin"){ // 判断是否为macOS系统
    if(message.statusBarIcon == "是"){
      app.dock.hide()
    }else if(message.statusBarIcon == "否"){
      app.dock.show()
    }
  }else if(process.platform == "win32"){ // 判断是否为windows系统
    if(message.statusBarIcon == "是"){
      win.setSkipTaskbar(true)
    }else if(message.statusBarIcon == "否"){
      win.setSkipTaskbar(false)
    }
  }
  win.webContents.send('message-to-parent', message);
});

3. electron菜单栏隐藏后还能被呼出的问题

由于electron的默认行为,新建窗口时,设置了autoHideMenuBar:true来隐藏默认的菜单栏,但是在windows系统里你还是可以通过按住键盘ctrl键唤出默认菜单栏(Mac系统没有这个问题,奇怪),只能通过novelWindow.removeMenu()方法来强制关闭默认菜单栏。

// 创建小说切换窗口
let novelWindow = null 
function changeNovelWindow(novalName) {
  novelWindow = new BrowserWindow({
    width: 460,
    height: 240,
    parent: newWindow,
    autoHideMenuBar:true,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: true, // 确保 nodeIntegration 为 false
    }
  });
  novelWindow.loadURL(`file://${__dirname}/novalChange.html?novalName=${novalName}`);
  // 完全移除菜单
  novelWindow.removeMenu();
  // novelWindow.webContents.openDevTools();
}

4. 关于应用置顶的问题

新建主窗口时,通过参数alwaysOnTop: true配置窗口始终位于顶层,但是在MacOs系统就不生效,需要setVisibleOnAllWorkspaces

const createWindow = async() => {
  let windowSizeRes = store.get("windowSize")
  win = new BrowserWindow({
    width: windowSizeRes?.width || 460,
    height: windowSizeRes?.height || 240,
    frame:false,
    alwaysOnTop: true, // 设置窗口始终位于顶层
    transparent: true, // 透明窗口
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      enableRemoteModule: false,
      contextIsolation: true,
      nodeIntegration: true // 确保 nodeIntegration 为 true
    }
  })

  // 在 macOS 上设置更高的置顶级别
  if (process.platform === 'darwin') {
    win.setAlwaysOnTop(true, 'floating');
    // 设置窗口级别高于普通窗口
    win.setVisibleOnAllWorkspaces(true, {
      visibleOnFullScreen: true
    });
  }
  
  win.loadFile('index.html')

5. 线上书源采集的问题

线上采集书源是遇到问题最多的模块^-^。一开始的想法时通过给不同的网站配置不同的规则来调用他们网站的接口来获取小说内容,后来发现但凡有点质量的小说网站都是需要登录,而且很多网站就算登录了想要通过接口访问获取小说内容还需要对应的app授权才行,更有甚者好多网站都有反爬虫机制。所以最终的方案就是在采集的小说网站注入采集代码,在主线程里使用cheerio来解析小说内容

image.png

  async searchNoval(novalName) {
    console.log("novalName=====>", novalName)
    this.novalArr = [];
    return new Promise((resolve, reject) => {
      fetch(this.searchNovalObj.searchNovaUrl, {
        "headers": this.searchNovalObj.headers,
        "body": this.searchNovalObj.body.replace("${novalName}", novalName),
        "method": this.searchNovalObj.method
      }).then(response => response.text()).then(data => {
        const $ = cheerio.load(data);
        let novalObj = {};
        $(".bookList .lastLine").each((index, elementList) => {
          novalObj = {
            bookSource:"塔读网"
          };
          $(".bookNm", elementList).each((index, element) => {
            novalObj.name = $(element).text();
          })
          $(".bot_list a:nth-child(2)", elementList).each((index, element) => {
            novalObj.bookUrl = $(element).attr("href");
          })
          $(".authorNm", elementList).each((index, element) => {
            novalObj.bookAuthor = $(element).text();
          })
          $(".condition span:nth-child(2)", elementList).each((index, element) => {
            novalObj.bookCatagory = $(element).text();
          })
          $(".condition span:nth-child(4)", elementList).each((index, element) => {
            novalObj.bookStatus = $(element).text();
          })
          $(".rtList .bookIntro", elementList).each((index, element) => {
            novalObj.bookDesc = $(element).text();
          })
          $(".bookImg img", elementList).each((index, element1) => {
            novalObj.imgUrl = $(element1).attr("data-src");
          })
          $(".bookrackBtn", elementList).each((index, element) => {
            novalObj.bookId = $(element).attr("data-bookid");
          })
          this.novalArr.push(novalObj);
          resolve(this.novalArr);
        });
      });
    })
  }

四、遗留问题:

  1. 苹果Mac系统把整个应用背景透明度设置的比较低的时,窗口大小改变后背景里会有"残影"这个问题真是无法解决,不知道是不是我MacOs系统的问题,有大佬知道这个是什么原因吗?

image.png

  1. 线上书源模块提取成公用模块

目前线上书源采集的代码还是写了很多参数,后续考虑把这个模块提取成一个公共模块