文章目录

  • 简介
    • 安装electron依赖
    • 本地数据库选择
      • indexedDB
        • 封装的库
      • SQLite
      • Lowdb
      • electron-store
      • electron-json-storage-alt、electron-json-storage
      • localstorage/sessionstorage
      • cookie
      • 小结
    • 自动升级
      • electron-updater
      • 自己写的升级模块
        • window 平台
        • mac 平台
          • 对于 asar文件
          • 对于 dmg 文件
    • 网络检查(window)
    • 主进程 http请求客户端
      • net模块封装
      • urllib封装
      • axios
      • request.js
    • 下载文件
      • 对于node http 模块封装
      • 对于urllib 的封装
    • 客户端日志
      • winston
      • electron-log
    • IPC 通讯(渲染window 发送消息给主进程)
      • ipcMain
        • method
        • demo
      • ipcRenderer
        • method
        • demo
    • IPC 通讯(主进程发送消息给渲染window)
    • 设置开机启动项
    • 其他应用唤醒客户端
    • 全局快捷键
    • 托盘
    • mac 应用菜单
    • 国际化
    • TouchBar
    • 硬件加速(mac)
    • 模式(development、production)
    • 崩溃日志发送
    • 单例模式
    • 白屏
    • electron bridge
    • BrowserWindow http请求拦截
    • 集成 vue 、react
      • vue
      • react
    • 无frame
      • window
      • mac
    • 设置DOM拖拽联通窗口拖拽
    • 代理设置
    • 打包发布
      • electron-builder
      • electron-packager
        • 优缺点
    • 项目地址

简介

在开发 electron 桌面端的时候,会遇到各种各样的坑,稍微总结下。

  • 安装electron依赖
  • 本地数据库选择
  • 自动升级
  • 网络检查(window)
  • 主进程http请求客户端
  • 下载文件
  • http请求客户端
  • IPC 通讯(渲染window 发送消息给主进程)
  • IPC 通讯(主进程发送消息给渲染window)
  • 设置开机启动项
  • 其他应用唤醒客户端
  • 全局快捷键
  • 托盘
  • 应用菜单(mac)
  • 国际化
  • TouchBar
  • 硬件加速(mac)
  • 模式(development、production)
  • 崩溃日志发送
  • 单例模式
  • 白屏
  • electron bridge
  • 集成 vue 、react
  • 代理设置
  • 打包发布

安装electron依赖

由于网络问题,导致 yarn 或者 npm 安装的时候,会报错

解决方案

sudo npm install -g cross-env
cross-env npm_config_electron_mirror="https://npm.taobao.org/mirrors/electron/" npm_config_electron_custom_dir="9.4.0" npm install

这里的 9.4.0 就是你所想安装的electron 版本。

本地数据库选择

本地数据库有很多可选的,

indexedDB

可以同时支持web端和桌面端使用。

IndexedDB是Chromium内置的一个基于JavaScript的面向对象的数据库,在Electron应用内它存储的容量限制与用户的磁盘容量有关,是用户磁盘大小的1/3.

特点:

  • NoSQL数据库,浏览器自带,可以储存大量数据,容量为250MB以上
  • 支持事务,有版本号的概念。
  • 支持较多的字段类型

封装的库

  • localForage,支持类Storage API语法的客户端数据存储polyfill,支持回退到Storage和Web SQL
  • dexie.js,提供更友好和简单的语法便于快速的编码开发,有Typescript支持。
  • ZangoDB,提供类MongoDB的接口实现,提供了许多MangoDB的特性实现
  • JsStore,提供基于indexedDB的类SQL的语法实现。

SQLite

关系型数据库,具有关系型数据库的一切特性,事务遵循ACID属性。小巧轻便,有knex这样的库做ORM。

是node原生模块,需要重新编译,而且有坑。

npm install sqlite3 --save
npm install electron-rebuild --save

执行 electron-rebuild,重新编译electron。

Lowdb

基于Loadsh的纯JSON文件数据库,速度较慢.

不支持索引/事务/批量操作等数据库功能

Small JSON database for Node, Electron and the browser. Powered by Lodash. ⚡️

electron-store

适合简单存储

electron-json-storage-alt、electron-json-storage

Easily write and read user settings in Electron apps

localstorage/sessionstorage

自带的存储。

坑点:

LocalStorage存储容量也很小,大概不会超过10M,它是以键值对形式保存数据的,同样也没有关联查询、条件查询的机制

SessionStorage最大的问题是,每次关闭应用程序,它里面的内容会被清空,想持久化存储数据,就不用考虑它了

cookie

Cookies存储容量太小,只能存4kb的内容,而且每次与服务端交互,同域下的Cookie还会被携带到服务端,也没有关联查询、条件查询的机制

小结

一般大型项目 首选 SQLite或者 indexedDB,如果一些用户设置上面的设置 可以通过 electron-json-storage-alt 实现。

自动升级

推荐使用electron-updater 或者自己写升级模块。

electron-updater

import { autoUpdater } from 'electron-updater';let downloading = false;function checkForUpdates() {if (downloading) {dialog.showMessageBox({type: 'info',buttons: ['OK'],title: pkg.name,message: `Downloading...`,detail: `Please leave the app open, the new version is downloading. You'll receive a new dialog when downloading is finished.`});return;}autoUpdater.checkForUpdates();
}autoUpdater.on('update-not-available', e => {dialog.showMessageBox({type: 'info',buttons: ['OK'],title: pkg.name,message: `${pkg.name} is up to date :)`,detail: `${pkg.name} ${pkg.version} is currently the newest version available, It looks like you're already rocking the latest version!`});console.log('Update not available.');
});autoUpdater.on('update-available', e => {downloading = true;checkForUpdates();
});autoUpdater.on('error', err => {dialog.showMessageBox({type: 'error',buttons: ['Cancel update'],title: pkg.name,message: `Failed to update ${pkg.name} :(`,detail: `An error occurred in retrieving update information, Please try again later.`,});downloading = false;console.error(err);
});autoUpdater.on('update-downloaded', info => {var { releaseNotes, releaseName } = info;var index = dialog.showMessageBox({type: 'info',buttons: ['Restart', 'Later'],title: pkg.name,message: `The new version has been downloaded. Please restart the application to apply the updates.`,detail: `${releaseName}\n\n${releaseNotes}`});downloading = false;if (index === 1) {return;}autoUpdater.quitAndInstall();setTimeout(() => {mainWindow = null;app.quit();});
});

自己写的升级模块

可以在项目启动或者页面激活状态的时候,请求服务器端最新版本号,如果存在版本过低的情况可以通知用户下载升级。

同样的是利用electron 下载文件,执行升级。

window 平台

执行执行升级exe

mac 平台

对于 asar文件

执行 child_processexec 调用 unzip -o latestPathMacZip 将 zip 文件解压成 latest.asar 文件,然后调用 renameSync 将原本的app.asar 修改成一个临时名字,将最新的latest.asar 修改成 app.asar 名字,然后最后删除掉临时名字就行了。

对于 dmg 文件

可以通过 mac 自带的hdiutil 指令。

网络检查(window)

一般的桌面端程序,都需要检查网络连接情况,友好的提示给客户。

在 window 里面可以借助network-interface,来监听网络连接情况。

https://www.npmjs.com/package/network-interface

const networkInterface = require('network-interface');networkInterface.addEventListener('wlan-status-changed', (error, data) => {if (error) {throw error;return;}console.log('event fired: wlan-status-changed');console.log(data);
});

主进程 http请求客户端

对于桌面端,主进程是会有http请求的。 例如升级啥的,或者下载文件等,都是需要主进程去执行http请求。

  • electron 封装的net模块
  • urllib
  • axios
  • request、request-promise

net模块封装

net 模块是一个发送 HTTP(S) 请求的客户端API。 它类似于Node.js的HTTP 和 HTTPS 模块 ,但它使用的是Chromium原生网络库来替代Node.js的实现,提供更好的网络代理支持。 It also supports checking network status.

net 的优势

  • 系统代理配置的自动管理, 支持 wpad 协议和代理 pac 配置文件。
  • HTTPS 请求的自动隧道。
  • 支持使用basic、digest、NTLM、Kerberos 或协商身份验证方案对代理进行身份验证。
  • 支持传输监控代理: 类似于Fiddler代理,用于访问控制和监视。
const { net } = require('electron')

urllib封装

const os = require('os');
const urllib = require('urllib');
const Agent = require('agentkeepalive');
const {HttpsAgent} = require('agentkeepalive');
const {electron: electronVersion} = process.versions;const config = {defaultArgs: {timeout: 30000,dataType: 'json',followRedirect: true},httpAgent: {keepAlive: true,freeSocketTimeout: 20000,maxSockets: Number.MAX_SAFE_INTEGER,maxFreeSockets: 256},httpsAgent: {keepAlive: true,freeSocketTimeout: 20000,maxSockets: Number.MAX_SAFE_INTEGER,maxFreeSockets: 256}
};class HttpClient extends urllib.HttpClient2 {constructor(app) {const {pkg} = app.config;super({defaultArgs: config.defaultArgs,agent: new Agent(config.httpAgent),httpsAgent: new HttpsAgent(config.httpsAgent)});this.app = app;this.logger = app.getLogger('httpClientLogger');this.UA = `${pkg.name}/${pkg.version};electron/${electronVersion};${encodeURIComponent(os.hostname())};${urllib.USER_AGENT}`;}async request(url, options = {}) {const {app} = this;const {host} = app.config || '';let request;options.headers = {"Content-Type": "application/json",referer: host,"user-agent": this.UA,...options.headers};const nowDate = Date.now();let error;try {return request = await super.request(url, options);} catch (e) {error = e;error.name = 'httpError';error.url = url;throw error;} finally {// 一次请求的时间差const timestamp = Date.now() - nowDate;// logger 日志记录console.log(timestamp);if (!options.disableLogger) {this.logger.info([url, options.method, request && request.status, error && error.message].join("^"));}}}
}module.exports = (app => {app.httpClient = new HttpClient(app);
})

axios

axios 是支持在node.js的。可以在electron 主进程中使用。

import axios from 'axios';axios.defaults.baseURL = process.env.VUE_APP_BASE_URL;
<!--强制使用node模块。-->
axios.defaults.adapter = require('axios/lib/adapters/http');// 请求拦截  设置统一header
axios.interceptors.request.use(config => {return config;},error => {console.log(error);return Promise.reject(error);}
);axios.interceptors.response.use(response => {return response;},error => {// console.error(error);return Promise.reject(error);}
);export default axios;

request.js

下载文件

文件下载,需要借助electron提供的getPath方法,一般默认的是下载目录下面

const downloadPath = electronApp.getPath('downloads');

对于node http 模块封装

const http = require('http');
const fs = require('fs');const downloadFileAsync = (uri, dest) => {return new Promise((resolve, reject) => {let file = '';try {// 确保dest路径存在,已经存在则删除重新下载if (fs.existsSync(dest)) {fs.unlinkSync(dest);}const path = process.platform === 'win32'? dest.slice(0, dest.lastIndexOf('\\')): dest.slice(0, dest.lastIndexOf('/'));fs.mkdirSync(path, { recursive: true });file = fs.createWriteStream(dest);} catch (e) {reject(e.message);}http.get(uri, (res) => {if (res.statusCode !== 200) {reject(response.statusCode);return;}res.on('end', () => {console.log('download end');});// 进度、超时等file.on('finish', () => {console.log('finish write file')file.close(resolve);}).on('error', (err) => {fs.unlink(dest);reject(err.message);})res.pipe(file);});});
}

对于urllib 的封装

业务的封装

const {app: electronApp, dialog} = require('electron');
const {createWriteStream} = require('fs');
const {parse} = require('url');
const path = require('path');const downloadFile = async (app, url) => {const downloadPath = electronApp.getPath('downloads');const {pathname} = parse(url);const fileName = pathname.split('/').pop();const localFilePath = path(downloadPath, fileName);const {canceled, filePath} = await dialog.showSaveDialog(app.mainWindow, {title: '保存附件',default: localFilePath})if (!canceled) {const savedFilePath = path.join(path.dirname(filePath), fileName);const writeSteam = createWriteStream(savedFilePath);const request = app.httpClient.request(url, {headers: {'Content-Type': null},streaming: true,followRedirect: true})const needShowProgress = Number(request.headers['content-length']) > 1048576;const downloadResponse = (type) => {// progressapp.mainWindow.webContents.send('download-progress', {type});}request.res.on("data", data => {writeSteam.write(data);if (needShowProgress) {downloadResponse('data');}});request.res.on('end', () => {writeSteam.end();downloadResponse('end');});request.res.on('error', () => {downloadResponse('error');})}
};

客户端日志

这里涉及到了,日志的本地存储,以及日志的打包上传到服务器。

市面上现在的技术方案

  • winston
  • electron-log

winston

所使用到的插件有winston-daily-rotate-filewinston-transport用于文件切割和远程上传。

对于 winston 的封装

const path = require('path');
const winston = require('winston');
require('winston-daily-rotate-file');
const Transport = require('winston-transport');
const {app: electronApp} = require('electron');
const {format} = winston;
const logReomteUrl = 'http://test.com/electron/log';const logger = function (options = {}) {return () => {const logDir = options.logDir || path.join(options.debug ? process.cwd() : electronApp.getPath('userData'), 'logs');const transportList = [new winston.transports.DailyRotateFile({dirname: path.join(logDir, options.name),filename: `${options.filename || options.name}-%DATE%.log`,maxSize: '15m',maxFiles: 7,createSymlink: true,symlinkName: `${options.symlinkName || options.name}.log`}),new class extends Transport {constructor(props) {super(props);this.options = props;}log(options = {}, callback) {if (process.env.DISABLE_LOG_REMOTE) {return;}const data = {type: this.options.name,message: `${options.timestamp} ${options.message}`};// 提交服务器端的日志地址。const url = logReomteUrl;// requestapp.httpClient.request(url, {method: 'POST',contentType: "json",data: data,disableLogger: true,}).catch(() => {});callback(null, true);}}(options)];<!--是否同步控制台输出-->if (process.env.CONSOLE_LOGGER) {transportList.push(new winston.transports.Console);}return new winston.createLogger({format: format.combine(format.label({label: options.name}),format.timestamp({format: "YYYY-MM-DD HH:mm:ss"}),format.splat(),format.simple(),format.printf(({level, timestamp, message, label}) => {const {tracert = {}, currentUser = {}} = options.currentContext || {};return [timestamp, level.toUpperCase(), `[${label}]`, tracert.traceId, currentUser.id, message].join("^")})),transports: transportList})}};
// 使用// electron 桌面端日志
app.electronLog = logger({name: "electron",symlinkName: 'app',debug: app.isDev
})
// 给web端调用的日志
app.webLogger = logger({name: "web",debug: app.isDev
});

electron-log

自行查询。

IPC 通讯(渲染window 发送消息给主进程)

借助electron 提供的 ipcMainipcRenderer 进行通讯。

ipcMain

method

  • on(channel,listener)
  • once(channel,listener)
  • removeListener(channel,listener)
  • removeAllListeners([channel])
  • handle(channel,listener)
  • handleonce(channel,listener)
  • removeHandler(channel)

demo

// 在主进程中.
const { ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {console.log(arg) // prints "ping"event.reply('asynchronous-reply', 'pong')
})ipcMain.on('synchronous-message', (event, arg) => {console.log(arg) // prints "ping"event.returnValue = 'pong'
})

ipcRenderer

method

  • on(channel,listener)
  • once(channel,listener)
  • removeListener(channel,listener)
  • removeAllListeners([channel])
  • send(channel,…args)
  • invoke(channel,…args)
  • sendSync(channel,…args)
  • postMessage(channel,message,[transfer])
  • sendTo(webContentsId,channel,…args)
  • sendToHost(channel,…args)

demo

//在渲染器进程 (网页) 中。
const { ipcRenderer } = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"ipcRenderer.on('asynchronous-reply', (event, arg) => {console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')

IPC 通讯(主进程发送消息给渲染window)

主要借助 BrowserWindow.webContents.sendipcRenderer.on方法

// 主进程
app.mainWindow.webContents.send('xxx','this is message');
// 渲染window
const {ipcRenderer} = require('electron');ipcRenderer.on('xxx',(event, message)=>{console.log(message) // this is message
})

设置开机启动项

主要是通过 setLoginItemSettings 实现

const {ipcMain, app: electronApp} = require('electron');
// 属性返回启动 Node.js 进程的可执行文件的绝对路径名。
const exePath = process.execPath;module.exports = (() => {// 给渲染页面获取当前的状态ipcMain.handle('get-auto-start-status', () => electronApp.getLoginItemSettings())// 设置开启自启ipcMain.on('auto-start-open', () => {electronApp.setLoginItemSettings({openAtLogin: true,path: exePath,args: []})});//设置开机不自启ipcMain.on('auto-start-closed', () => {electronApp.setLoginItemSettings({openAtLogin: false,path: exePath,args: []})})});

其他应用唤醒客户端

主要是借助setAsDefaultProtocolClient 实现其他应用唤起客户端。

const {app: electronApp, dialog} = require('electron');
const path = require('path');
const SCHEMA_NAME = 'xx'; // 协议前缀if (process.defaultApp) {if (process.argv.length >= 2) {electronApp.setAsDefaultProtocolClient(SCHEMA_NAME, process.execPath, [path.resolve(process.argv[1])]);}
} else {electronApp.setAsDefaultProtocolClient(SCHEMA_NAME);
}electronApp.on('open-url', ((event, url) => {dialog.showErrorBox("Welcome Back", `You arrived from: ${url}`);
}));

全局快捷键

借助 globalShortcut 方法实现


const {globalShortcut} = require("electron");module.exports = (app => {// 注册全局快捷键globalShortcut.register("CommandOrControl+Option+Y", () => {app.mainWindow.show()})
});

托盘

需要考虑window 系统和macOS 系统,对于macOS 还需要考虑是不是暗黑模式。

对于mac 还有

const macLightIcon = path.join(__dirname, "../../../dist-assets/tray/tray@2x.png");
const macDarkIcon = path.join(__dirname, "../../../dist-assets/tray/tray-active@2x.png");
const winLightIcon = path.join(__dirname, "../../../dist-assets/tray/tray-windows.png");

mac 应用菜单

对于mac 端的左上角也有一个应用的菜单,主要是通过 Menu.setApplicationMenu(Menu.buildFromTemplate([])) 实现的

对于 tabs 数组

const tabs = [
{label: 'Application',submenu:[{label:'xxxx',accelerator: "CommandOrControl+,",click:()=>{// }},{type: "separator"}, // 一根线{label:'xxx2',accelerator: "CommandOrControl+Q",click:()=>{// }}]
}
]

国际化

默认提供的是中文,然后通过配置文件,来获取到其他语言的翻译

// en-US
module.exports = {"测试": "Test","关于xx": "About xx","退出": "Quit","调试": "Debug","窗口": "Window","托盘":'Tray'
};

然后在app 方法里面绑定一个方法

const en = require('../locales/en-US');// 匹配一个单字字符(字母、数字或者下划线)。等价于 [A-Za-z0-9_]。
const interpolate_reg = /\{(\w*)\}/g;// replace data
const replaceData = (key, lang) => {return key.replace(interpolate_reg, value => {const tempKey = value.slice(1, value.length - 1);return lang[tempKey] ? lang[tempKey] : key;})
};module.exports = (app => {// 绑定 t 方法。app.t = ((title, lang = {}) => {// 绑定到app 上面的语言。if (app.locale.startsWith('zh')) {return replaceData(title, lang);}const enLang = en[title];return enLang ? replaceData(enLang, lang) : title;})
});

然后在调用的时候。

app.t('关于xx') //这样就会根据不同的语言情况。获取到不同翻译了。

然后在启动项目的时候,渲染线程,拉取桌面端的语言,然后根据语言更新window 端的语言。

// 在主线程里面

 ipcMain.handle('get-app-config', async () => {const locale = await app.getLocale();return {timestamp: (new Date).getTime(),locale: locale,...app.config}
});
// 在渲染window 里面 import {isElectron, getAppConfig, addEventListener, EVENTS} from "./utils/electron";async created() {if (isElectron) {const appConfig = await getAppConfig();console.log(appConfig);// this.$store.commit('UPDATE_ELECTRON_CONFIG', appConfig);}
},

TouchBar

主要借助 TouchBarTouchBarButton 接口注册进去

每个 TouchBarButton 参数有 label,backgroundColor, click

最后通过 win.setTouchBar(TouchBar) 注册进去。

硬件加速(mac)

mac版本的的electron 可能会出现花屏现状,解决方案就是关闭硬件加速。

通过 disableHardwareAcceleration实现。

模式(development、production)

通过注入 process.env.XXX 这里 XXX 是通过 cross-env 组件注入进去的。

在启动electron的时候

{"scripts":{"dev":"cross-env NODE_ENV=development electron ."}
}

这样就可以在代码里面通过 process.env.NODE_ENV 获取了。

this.isDev = "development" === process.env.NODE_ENV;

崩溃日志发送

const {crashReporter} = require('electron');crashReporter.start({productName: "test",companyName: "test",submitURL: "https://www.test.com",autoSubmit: true,uploadToServer: true,ignoreSystemCrashHandler: true
});

单例模式

通过requestSingleInstanceLock方法,来实现 单例模式

const singleInstanceLock = electronApp.requestSingleInstanceLock();if (singleInstanceLock) {//electronApp.on('second-instance', () => {//  查看打开的是否是 login window 还是 main windowapp.loginWindow &&!app.loginWindow.isDestroyed() ?(app.loginWindow.isVisible() || app.loginWindow.show(),app.loginWindow.isMinimized() && app.loginWindow.restore(),app.loginWindow.focus()) :app.mainWindow &&(app.mainWindow.isVisible() || app.mainWindow.show(),app.mainWindow.isMinimized() && app.mainWindow.restore(),app.mainWindow.focus())});// 监听  ready 事件electronApp.on('ready', async () => {<!--app 的初始化操作-->await app.init();<!--唤起登录,自动登录,选择登录。啥的-->app.launchLogin();});} else {electronApp.quit();
}

白屏

在项目启动的时候,需要先用 loading.html 页面加载,防止出现白屏现象。

依赖 BrowserViewBrowserWindow

新建完 BrowserWindow 之后,利用BrowserView 加载loading页面,监听BrowserWindow.webContens的dom-ready 事件。移除掉BrowserView

const {BrowserView, BrowserWindow} = require('electron');const browserWindow = new BrowserWindow({width: 500,height: 600,// 其他参数
});const loadingBrowserView = new BrowserView();browserWindow.setBrowserView(loadingBrowserView);loadingBrowserView.setBounds({x: 0,y: 0,width: 400,height: 600
});loadingBrowserView.webContents.loadURL('loading.html');browserWindow.webContents.on('will-navigate', () => {browserWindow.setBrowserView(loadingBrowserView);
});browserWindow.webContents.on('dom-ready', async (event) => {browserWindow.removeBrowserView(loadingBrowserView);
});

electron bridge

通过在 new BrowserWindow的时候,传递参数 webPreferences.preload 这样就可以设置electron-bridge了。

在页面运行其他脚本之前预先加载指定的脚本 无论页面是否集成Node, 此脚本都可以访问所有Node API 脚本路径为文件的绝对路径。 当 node integration 关闭时, 预加载的脚本将从全局范围重新引入node的全局引用标志

通过这种方式,就可以实现兼容web端桌面端的应用了。

BrowserWindow http请求拦截

通过监听webContents.session.webRequest.onBeforeRequestwebContents.session.webRequest.onBeforeSendHeaders 就可以了。

 // 需要拦截的请求。
const webRequestFilter = {urls: ["*://test.aaa.com/*", "*://*.ccc.com/*"]
};browserWindow.webContents.session.webRequest.onBeforeRequest(webRequestFilter, (details, callback) => {// 监听 before request// 是否存在下载if (details.url.includes('desktop-download')) {downloadFile(app, details.url);}// 原始请求被阻止发送或完成,而不是重定向到给定的URLcallback({redirectURL: details.redirectURL});
});browserWindow.webContents.session.webRequest.onBeforeSendHeaders(webRequestFilter, (details, callback) => {// 绑定header 头部。if (details.requestHeaders.Cookie) {const {ctoken = ''} = cookieParse(details.requestHeaders.Cookie);if (ctoken) {details.requestHeaders['x-csrf-token'] = ctoken;}}// When provided, request will be made with these headers.callback({requestHeaders: details.requestHeaders});
});

集成 vue 、react

vue

主要通过 electron-vue 传送门: github

react

主要是通过 electron-react-boilerplate 传送门:github

无frame

只需要在创建 BrowserWindow 的时候,参数 frame 为false 就可以了。

const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 600, frame: false })
win.show()

window

就需要自定义实现右上角最大、最小、关闭

<header class="drag-area"><div class="header-actions"><div @click="handleMinWindow"><svg t="1586443316286" className="icon-min" viewBox="0 0 1024 1024" version="1.1"><defs><style type="text/css"></style></defs><pathd="M65.23884 456.152041 958.760137 456.152041l0 111.695918L65.23884 567.847959 65.23884 456.152041z"p-id="1094"></path></svg></div><div @click="handleUnMaxWindow" v-if="isWinMax"><svg t="1586445181598" className="icon" viewBox="0 0 1157 1024" version="1.1"><defs><style type="text/css"></style></defs><pathd="M1086.033752 753.710082 878.220684 753.710082 878.220684 951.774989 878.220684 1021.784509 878.220684 1023.113804 808.211164 1023.113804 808.211164 1021.784509 70.895716 1021.784509 70.895716 1023.113804 0.886196 1023.113804 0.886196 1021.784509 0.886196 951.774989 0.886196 339.413241 0.886196 269.403721 70.895716 269.403721 269.403721 269.403721 269.403721 0.886196 274.277802 0.886196 339.413241 0.886196 1086.033752 0.886196 1151.612289 0.886196 1156.043271 0.886196 1156.043271 683.700563 1156.043271 753.710082 1086.033752 753.710082ZM70.895716 951.774989 808.211164 951.774989 808.211164 753.710082 808.211164 683.700563 808.211164 339.413241 70.895716 339.413241 70.895716 951.774989ZM1086.033752 70.895716 339.413241 70.895716 339.413241 269.403721 808.211164 269.403721 878.220684 269.403721 878.220684 339.413241 878.220684 683.700563 1086.033752 683.700563 1086.033752 70.895716Z"p-id="2415"></path></svg></div><div @click="handleMaxWindow" v-else><svg t="1586443335243" className="icon-max" viewBox="0 0 1024 1024" version="1.1"><defs><style type="text/css"></style></defs><pathd="M128.576377 895.420553 128.576377 128.578424l766.846222 0 0 766.842129L128.576377 895.420553zM799.567461 224.434585 224.432539 224.434585l0 575.134923 575.134923 0L799.567461 224.434585z"p-id="1340"></path></svg></div><div @click="handleCloseWindow"><svg t="1586443316286" className="icon-close" viewBox="0 0 1024 1024"version="1.1"><pathd="M895.423623 224.432539 607.855138 512l286.901289 297.699216 0.666172 85.723384-95.856162 0L512 607.856162 224.432539 895.423623l-95.856162 0 0-95.856162 287.567461-287.567461L128.576377 224.432539l0-95.856162 95.856162 0 287.567461 287.567461 287.567461-287.567461 95.856162 0L895.423623 224.432539z"p-id="1217"></path></svg></div></div></header>
.drag-area {-webkit-app-region: drag;-webkit-user-select: none;user-select: none;z-index: 500;width: 100vw;
}

mac

顶部需要设置一条可以拖拽的区域

.drag-area {-webkit-app-region: drag;-webkit-user-select: none;user-select: none;z-index: 500;width: 100vw;background-color: transparent;height: 18px;position: fixed;
}

设置DOM拖拽联通窗口拖拽

.drag-area {-webkit-app-region: drag; // 支持窗口拖拽-webkit-user-select: none;user-select: none;z-index: 500;
}

代理设置

待定。。。

打包发布

主要的打包方案 如下:

  • electron-builder
  • electron-packager

electron-builder

通过在 package.json 里面配置

{"scripts":{"build":"electron-builder"
}"build":{"productName":"productName","appId":"appId","directories":{"output": "output"},"files":[],"nsis":{},"dmg":{},"mac":{},"win":{},"linux":{}
}
}

electron-packager

打包参数:

electron-packager <sourcedir> <appname> <platform> <architecture> <electron version> <optional options>
  • sourcedir:项目所在路径
  • appname:应用名称
  • platform:确定了你要构建哪个平台的应用(Windows、Mac 还是 Linux)
  • architecture:决定了使用 x86 还是 x64 还是两个架构都用
  • electron version:electron 的版本
  • optional options:可选选项

优缺点

1、支持平台有:Windows (32/64 bit)、OS X (also known as macOS)、Linux (x86/x86_64);
2、进行应用更新时,使用electron内置的autoUpdate进行更新
3、支持CLI和JS API两种使用方式;

项目地址

为此,我将业务系统里面用到的各个客户端需要的功能,剥离了出来,新建了一个template 方便新业务系统的开发。

编码不易,欢迎star

https://github.com/bosscheng/electron-app-template

github

electron 桌面端业务中的小结(坑)相关推荐

  1. 实战解读增长黑客在 B 端业务中的应用

    姜菡钰(卡爷),网易云信及网易七鱼市场总监,拥有十余年 B 端与 C 端产品运营推广经验,对于增长黑客在 B 端业务中的运用颇有心得. 本文来源于第七届 TOP 100 全球软件案例研究峰会演讲实录, ...

  2. 【ChatGPT】ChatGPT 在电商B端业务中的应用落地场景产品化思考

    ChatGPT 在电商B端业务中的应用落地场景产品化思考 文章目录 ChatGPT 在电商B端业务中的应用落地场景产品化思考 电商B端业务的本质思考 电商B端业务的本质特征 1. 交易规模较大 2. ...

  3. Electron-Vue3-Vadmin后台系统|vite2+electron桌面端权限管理系统

    基于vite2.x+electron12桌面端后台管理系统Vite2ElectronVAdmin. 继上一次分享vite2整合electron搭建后台框架,这次带来的是最新开发的跨桌面中后台权限管理系 ...

  4. electron 打包 php,electron 将现有vue项目改成支持electron桌面端应用

    vue过滤器和组件化 !DOCTYPE htmlhtml lang="en"head meta charset="UTF-8" meta name=" ...

  5. Electron桌面端开发(进程)

    Electron中的进程 进程 前言

  6. 流量难、获客难、增长难?增长黑客思维“解救”B端业务

    随着市场竞争的不断加剧,流量越来越贵.留存与转化越来越难,实现用户和业务的增长并不容易.无论是B2C 还是B2B的企业,都可能遇到增长的挑战.对于营销团队而言,传统的漏斗式营销思维已有些力不从心,需要 ...

  7. 网易云信七鱼市场总监姜菡钰:实战解读增长黑客在B端业务的运用

    近些年 ,随着互联网的迅速崛起,"增长黑客"一词逐渐映入大众的眼帘,并成为了最热门的话题之一.从2018年开始,线上流量触达天花板,引流之争的激烈程度空前高涨,企业为了获得更多的关 ...

  8. 美团“互联网下半场”的筹码:豪赌B端业务

    互联网巨头的目光正越来越多地投向B端,除了BAT,华为.美团等都已置身于toB的战局里. 实际上,"互联网下半场"这一提法,最早来自于美团点评CEO王兴.早在2016年,王兴就发表 ...

  9. 得物商家客服桌面端Electron技术实践

    1.业务背景 随着公司业务的快速发展,商家客服也纳入了我们的服务范围,商家客服工作台的定位是通过工具和数据服务商家,一站式解决用户购买咨询诉求.通过工具和运营策略协助商家提升服务品质,让品牌商家有动力 ...

最新文章

  1. GlideApp 引入不了问题
  2. 聊聊工业界做机器学习的里程碑
  3. Filter过滤要登录的页面(重要)
  4. 9203-1204-抄写
  5. Angular2 小贴士 Name
  6. OpenGL笔记4 数据传递二
  7. 【图像目标检测】电力图像数据集:电力巡检智能缺陷检测(有下载链接)
  8. 四元数与欧拉角的转换与使用matlab的simulink搭建实现
  9. 开机出现grub rescue,修复办法
  10. eleme项目框架搭建及eslint常见报错(含图标字体库说明)
  11. IDEA debug或启动报错:maven-resources-production:XXX:java.lang.NegativeArraySizeException
  12. 海量数据荣获华为“中国政企数据存储优秀合作伙伴”金奖
  13. 下载到的电子书格式是Mobi,这种格式能否在WINDOWS电脑上打开?
  14. 台湾大学郭彦甫matlab百度云,台湾国立大学郭彦甫Matlab教程笔记(23) linear systems...
  15. 马士兵学习笔记-Java基础网络编程
  16. 2021最新个人主页源码-靳吉朕的个人主页
  17. mysql 数据库 期末复习题库
  18. 【校招】面试_字节跳动_客户端开发工程师_一面
  19. python学习笔记之三——MakeHuman源码阅读
  20. 基于Python的复杂环境中车道线自动检测系统

热门文章

  1. Audio系列之静音控制
  2. 网名接龙之--绝缘材料
  3. 华为交换机重制_华为交换机恢复出厂设置操作步骤
  4. idea的常用设置和插件
  5. 关于time.clock()废弃
  6. 核心频率个加速频率_255W TDP不算啥,英特尔14核酷睿i9-9990XE处理器全核加速5.0GHz...
  7. 预售票房破亿,《灌篮高手》电影成功的背后是怎么宣传的
  8. 【Access】win 10 / win 11:Access 下载、安装、使用教程(「管理信息系统」实践专用软件)
  9. UOS如何进入livecd模式
  10. 夸域单点登录解决方案