Electron选择文件文件夹对话框非原创传播

文章转载自:https://www.jianshu.com/p/e71...,感谢文章作者,成功完成选择文件夹 的功能 1.第一种方法,纯js代码其原理是:利用input标签的file类别,打开选择文件对话框通过input标签的change事件,将选择的文件返回。为了使每次选择的文件都得到更新,在input对象用完后每次都移除出html中,下次使用时再重新创建并添加到html中。代码如下:/**         *按钮事件实现函数         *原理:利用input标签的file类别,打开选择文件对话框         *通过change事件,将选择的文件返回。为了使每次选择的文件都得到更新,         *在input对象用完后每次都移除出html中,下次使用时再重新创建并添加到html中         */        btnClickFun:function(dir){            // 创建input标签            var inputObj=document.createElement('input')                // 设置属性                inputObj.setAttribute('id','_ef');                inputObj.setAttribute('type','file');                inputObj.setAttribute("style",'visibility:hidden');                if(dir){ // 如果要选择路径,则添加以下两个属性                    inputObj.setAttribute('webkitdirectory', "");                    inputObj.setAttribute('directory', "");                }                // 添加到DOM中                document.body.appendChild(inputObj);                // 添加事件监听器                inputObj.addEventListener("change",this.updatePath);                // 模拟点击                inputObj.click();        },        // 打开文件选择器input标签的change事件响应        updatePath:function(){            var inputObj = document.getElementById("_ef");            var files = inputObj.files;            console.log(files)            try{                if(files.length > 1){                    alert('当前仅支持选择一个文件')                }                else{                    switch(this.btntype){                        case 'store':                            // 临时变量的值赋给输出路径                            this.outpath = files[0].path;                            break;                        case 'add':                            // 添加文件操作                            this.filepath = files[0].path;                            if(this.addFile(this.filepath)){                                alert("添加成功")                            }                            break;                            default:                            break;                    }                }                // 移除事件监听器                inputObj.removeEventListener("change",function(){});                // 从DOM中移除input                document.body.removeChild(inputObj);            }catch (error) {                alert(error)            }        },btnClickFun函数中创建并设置了input标签属性及监听器,函数updatePath为change事件监听的回调函数。通过input标签对象的files属性获得选中的文件名。2.第二种方法,electron首先在子进程中引入ipcRenderer模块,import {ipcRenderer} from 'electron'利用该模块向主进程发送“open-directory-dialog”消息,配置参数为“openDirectory”或“openFile”,并且设置主进程返回的消息“selectedItem”的回调函数为getPath,// 按钮点击事件        btnClick:function(type){           this.btntype = type;            // 判断点击事件是哪个按钮发出的            switch(type){                case 'store':                // 选择存贮路径                    //this.btnClickFun(true);                    ipcRenderer.send('open-directory-dialog','openDirectory');                    ipcRenderer.on('selectedItem',this.getPath);                    break;                case 'add':                // 添加文件                    //this.btnClickFun(false);                    ipcRenderer.send('open-directory-dialog','openFile');                    ipcRenderer.on('selectedItem',this.getPath);                    break;                case 'remove':                    this.deleteItem();                    break;                case 'pack':                    break;                    default:                    break;            }        },        getPath:function(e,path){            console.log(path)            if(path == null){                    alert('请选择一个文件/文件夹')            }            else{                switch(this.btntype){                    case 'store':                        // 临时变量的值赋给输出路径                        this.outpath = path;                        break;                    case 'add':                        // 添加文件操作                        this.filepath = path;                        if(this.addFile(this.filepath)){                            alert("添加成功")                        }                        break;                        default:                        break;                }            }        },然后在主进程中设置好子进程的消息监听,并且引入dialog模块import { dialog } from 'electron'// 绑定打开对话框事件ipcMain.on('open-directory-dialog', function (event,p) {  dialog.showOpenDialog({    properties: [p]  },function (files) {      if (files){// 如果有选中        // 发送选择的对象给子进程        event.sender.send('selectedItem', files[0])      }  })});这样就可以完成选择文件/文件夹的操作了。3.第一种方法实现的vue组件纯JS实现的文件选择器,需要操作DOM原理:利用input标签的file类别,打开选择文件对话框通过change事件,将选择的文件返回。为了使每次选择的文件都得到更新,在input对象用完后每次都移除出html中,下次使用时再重新创建并添加到html中默认打开文件夹,如果需要选择文件,则需要在调用处配置属性dir='file'属性caption显示按钮的文本信息成功调用后会向父进程发送一个‘btnSelectItem’消息用于返回选中的文件全路径 ...

June 26, 2019 · 2 min · jiezi

electron-vue桌面应用入门实例

从零开始搭建,基于electron vue cli3 的桌面应用。github 本人刚入坑,项目适合新手,客官来了轻踩。若是能遇到高手对小女子我指点一二,更是感恩不尽。 在这里你可以找到的有从零开始搭建一个项目使用vue-cli3搭建electron项目简单的实现一个主线程和渲染层的交互查到的资料大部分都是vue cli2的版本,我们来探索新的版本吧 首先新建vue项目 vue create electron-vue-examplecd electron-vue-examplenpm run serve vue项目的建立非常的简单,这里就不再巴拉巴拉了~ 安装vue-cli-plugin-electron-builder接下来就是把让我们的项目可以被打包成桌面应用,这里我们用到了[vue-cli-plugin-electron-builder] 它是一个vue cli3插件,帮你配置electron-build。 第一步,在根目录下运用下面的命令 vue add electron-builder 第二步,运行启动命令 npm run electron:serve复制代码实现一个图片列表我们实现一个vue列表 接下来,修改src/background.js添加一个新的窗口添加一个新的窗口给他 接下来是点击图片展示在另外一个页面,用到了通信ipcMain 模块,一边发送一边接受,通过arg来传递参数。vue点击事件里发送图片的url信息到background.js主进程,主进程监听事件,然后打开窗口传递信息到image.vue页面 background.js头部添加 ipcMain import { app, protocol, BrowserWindow, ipcMain } from 'electron'复制代码background.js最后添加 //ipcMain 模块有如下监听事件方法:// 监听 组件@/compontents/ImageList.vue methods:openImage下的ipcRenderer.send("toggle-image", image)// render 发送消息,main 接收消息//ipcMain.on('toggle-image', (event, arg) => { imageWindow.show() // 拿到消息后再发送给@/views/image.vue中的 ipcRenderer.on('image'... imageWindow.webContents.send('image', arg)})复制代码实现菜单配置在@/compontents/ImageList.vue中添加方法 initMenu() { // 初始化菜单 const menu = Menu.buildFromTemplate([ { label: "文件", submenu: [ { label: "设置", accelerator: "CmdOrCtrl+,", click: () => { ipcRenderer.send("toggle-about"); } }, { type: "separator" }, { label: "退出", accelerator: "CmdOrCtrl+Q" } ] }, { label: "演示菜单", submenu: [ { label: "菜单 1" }, { label: "菜单 2" }, { label: "菜单 3" } ] } ]); Menu.setApplicationMenu(menu); },主要演示这两个小功能,需要注意的一点是,路由需要用Hash,后面研究一下,怎么用history实现。第一次接触electron的同学,一定会疑惑如何主程序和渲染程序做交互,这个例子就简单的介绍了。 ...

June 21, 2019 · 1 min · jiezi

Electronvue开发实战之Electron入门

原文首发于我的博客,欢迎点击查看获得更好的阅读体验~前言最近想学点新东西,对桌面端有些兴趣,就想学学Electron,想一个新的东西就得有一个项目作为实战基础,这样才有学习的动力与目标。 Electron简要介绍见:Electron简要介绍 Electron-vue简要介绍见:Electron-vue简要介绍 项目搭建# 如果你没有vue-cli的话需要全局安装npm install -g vue-cli# 然后使用vue-cli来安装electron-vue的模板vue init simulatedgreg/electron-vue my-project# 安装依赖cd my-projectnpm install # or yarn# 进入开发模式npm run dev # or yarn run dev如果你是windows用户,在安装期间遇到了关于node-gyp、C++库等方面的问题的话,请参考官方文档给出的解决办法。 如果上述都没有问题,那么你将会看到如下界面: 过五关斩六将以上为理想状态,让我们看看现实是如何的残酷。背景因为一些原因在前段时间将Nodejs版本从v8.3.0升级到稳定版本v10.16.0。稳定版嘛,以为一切正常,然后npm install老项目的时候node-sass安装不上,最后就把Nodejs升级到了v12.3.1的开发版,最后就一切正常了。 以下是在如下环境下进行的操作: 科学上网nodejs v12.3.1npm 忘记记录了windows 10 专业版踩坑开始首先看一下,我npm install所遇到的错误: 原文链接经分析发现,某些版本下,chromedriver 的 zip 文件 url 的响应是 302 跳转,而在 install.js 里使用的是 Node.js 内置的 http 对象的 get 方法无法处理 302 跳转的情况;而在另外一些情况下,则是因为 googleapis.com 被墙了,此时即使采用科学上网的方法也仍然无法获取文件。 无论是上述哪种情况,可以使用下面的命令安装:(使用 cnpm 安装亦可) npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver然后以为一切正常,就npm run dev,结果可想而知,在运行日志中报错了。错误日志忘记截图保存了,大概意思提示就是electron没有正确安装,需要删除node_modules文件夹重新npm install。哦霍~那就再来呗,然并卵。最后想到也可能和安装chromedriver一样的问题,所以就找到了下在的命令安装: ...

June 15, 2019 · 2 min · jiezi

Electron酷家乐客户端开发实践分享-下载管理器

作者:钟离,酷家乐PC客户端负责人原文地址:https://webfe.kujiale.com/electron-ku-jia-le-ke-hu-duan-kai-fa-shi-jian-fen-xiang-jin-cheng-tong-xin/酷家乐客户端:下载地址 https://www.kujiale.com/activity/136文章背景:在酷家乐客户端在V12改版成功后,我们积累了许多的宝贵的经验和最佳实践。前端社区里关于Electron知识相对较少,因此希望将这些内容以系列文章的形式分享出来。系列文章: 【Electron】酷家乐客户端开发实践分享 — 入坑篇【Electron】酷家乐客户端开发实践分享 — 软件自动更新【Electron】酷家乐客户端开发实践分享 — 浏览器启动客户端【Electron】酷家乐客户端开发实践分享 — 进程通信【Electron】酷家乐客户端开发实践分享 — 下载管理器不定期更新...背景打开酷家乐客户端,可以在左下角的更多菜单中找到下载管理这个功能,今天我们就来看看在Electron中如何实现一个下载管理器。 如何触发下载行为由于Electron渲染层是基于chromium的,触发下载的逻辑和chromium是一致的,页面中的a标签或者js跳转等等行为都可能触发下载,具体视访问的资源而定。什么样的资源会触发浏览器的下载行为呢? response header中的Content-Disposition为attachment。参考MDN Content-Dispositionresponse header中的Content-Type,是浏览器无法直接打开的文件类型,例如application/octet-stream,此时取决于浏览器的具体实现了。例子: IE无法打开pdf文件,chrome可以直接打开pdf文件,因此pdf类型的url在chrome上可以直接打开,而在IE下会触发下载行为。在Electron中还有一种方法可以触发下载: webContents.download。相当于直接调用chromium底层的下载逻辑,忽略headers中的那些判断,直接下载。 上述两种下载行为,都会触发session的will-download事件,在这里可以获取到关键的downloadItem对象 整体流程 设置文件路径如果不做任何处理的话,触发下载行为时Electron会弹出一个系统dialog,让用户来选择文件存放的目录。这个体验并不好,因此我们首先需要把这个系统dialog去掉。使用downloadItem.savePath即可。 // Set the save path, making Electron not to prompt a save dialog.downloadItem.setSavePath('/tmp/save.pdf');为文件设置默认下载路径,就需要考虑文件名重复的情况,一般来说会使用文件名自增的逻辑,例如:test.jpg、test.jpg(1)这种格式。文件默认存放目录,也是一个问题,我们统一使用app.getPath('downloads')作为文件下载目录。为了用户体验,后续提供修改文件下载目录功能即可。 // in main.js 主进程中const { session } = require('electron');session.defaultSession.on('will-download', async (event, item) => { const fileName = item.getFilename(); const url = item.getURL(); const startTime = item.getStartTime(); const initialState = item.getState(); const downloadPath = app.getPath('downloads'); let fileNum = 0; let savePath = path.join(downloadPath, fileName); // savePath基础信息 const ext = path.extname(savePath); const name = path.basename(savePath, ext); const dir = path.dirname(savePath); // 文件名自增逻辑 while (fs.pathExistsSync(savePath)) { fileNum += 1; savePath = path.format({ dir, ext, name: `${name}(${fileNum})`, }); } // 设置下载目录,阻止系统dialog的出现 item.setSavePath(savePath); // 通知渲染进程,有一个新的下载任务 win.webContents.send('new-download-item', { savePath, url, startTime, state: initialState, paused: item.isPaused(), totalBytes: item.getTotalBytes(), receivedBytes: item.getReceivedBytes(), }); // 下载任务更新 item.on('updated', (e, state) => { // eslint-disable-line win.webContents.send('download-item-updated', { startTime, state, totalBytes: item.getTotalBytes(), receivedBytes: item.getReceivedBytes(), paused: item.isPaused(), }); }); // 下载任务完成 item.on('done', (e, state) => { // eslint-disable-line win.webContents.send('download-item-done', { startTime, state, }); }); });现在触发下载行为,文件就已经会下载到Downloads目录了,文件名带有自增逻辑。同时,对下载窗口发送了关键事件,下载窗口可以根据这些事件和数据,创建、更新下载任务。 ...

June 14, 2019 · 2 min · jiezi

Electron酷家乐客户端开发实践分享-入坑篇

作者:钟离,酷家乐PC客户端负责人原文地址:https://webfe.kujiale.com/electron-ku-jia-le-ke-hu-duan-kai-fa-shi-jian-fen-xiang-ru-keng-zhi-nan/酷家乐客户端:下载地址 https://www.kujiale.com/activity/136文章背景:在酷家乐客户端在V12改版成功后,我们积累了许多的宝贵的经验和最佳实践。前端社区里关于Electron知识相对较少,因此希望将这些内容以系列文章的形式分享出来。系列文章: 【Electron】酷家乐客户端开发实践分享 — 入坑篇【Electron】酷家乐客户端开发实践分享 — 软件自动更新【Electron】酷家乐客户端开发实践分享 — 浏览器启动客户端【Electron】酷家乐客户端开发实践分享 — 进程通信不定期更新...本文的初衷Electron所使用的技术栈(JavaScript、NodeJs、HTML、CSS)和web前端工程师完美契合。于是,越来越多的前端工程师,用Electron来开发桌面客户端的开发,我也是其中的一员。 虽然Electron技术栈对前端工程师比较友好,但是概念较多,和web前端开发还是有很大差别的,写个入坑指南希望能帮助读者快速上手Electron。 了解客户端首先抛出一个问题,web应用是桌面客户端吗?显然不是。那么,问题来了,什么样的软件才是桌面客户端呢?我们既然要从web前端转到客户端开发,那么就需要了解客户端,就像我们当初了解web应用一样。 回到刚刚那个问题,桌面客户端有两个重要的特点: 独立运行于操作系统上(桌面客户端只是PC,那么限定windows、MacOS、linux这几个主流PC操作系统)有自己的GUI(用户图形界面 graphical user interface)web应用有自己的GUI,必须在浏览器中执行,因此不是桌面客户端。 浏览器能直接运行在操作系统上,而且有自己的GUI,因此浏览器是桌面客户端。 Electron的能力在刚刚接触Electorn的时候,文档看的我是眼花缭乱。在某个加班的深夜,我不禁对天长叹:这个东西到底能干啥? 这东西能干啥?在经历了Electron的反复摩擦之后,我总结了Electron的几个关键能力: NodeJs全部能力,与操作系统交互 operation system 与操作系统相关的操作HTTP(s)、HTTP2process、child process 进程相关file system 文件系统...省略Electron提供的基础模块,主要与操作系统交互 app 主进程声明周期管理,控制MacOS任务栏dock、windows任务栏taskbarBrowserWindow 控制窗口,在MacOS和windows中窗口非常重要!screen 操作用户显示器globalShortcut 系统级别快捷键...省略Chromium提供的能力,主要提供GUI图形界面 解析HTML、CSS、JSajax请求cookie、localstorage...省略能力越大,责任越大如果用户安装了我们的桌面客户端,那么我们的软件在用户电脑上运行时,就有了非常大的权利,这是把双刃剑。 用户选择了我们的软件,我们也要对用户的电脑负责。能力越大,责任也就越大: 1.注意内存的占用,特别是chromium,简直是内存怪兽。可以通过os来获取用户电脑的配置,然后根据电脑的配置和可用资源,来制定合理的策略。 为软件增加代码签名,提升安全性谨慎操作注册表、用户敏感目录一旦被贴上【流氓软件】、【不好用】的标签,就很难再改变用户的印象了。 主进程、渲染进程 生命周期主进程:从整个应用启动到结束,该进程一直存在。主进程只有一个。 渲染进程:主进程可用创建/销毁渲染进程,因此渲染进程的生命周期是不固定的。渲染进程可以有多个。 执行环境 在Electron的API文档中,会在文档顶部标识该模块在哪个进程可用,例如:ipcRenderer 职责划分主进程渲染进程控制app的生命周期,为app注册关键事件解析HTML,渲染窗口内容阻止一些默认行为,例如webContents的跳转、download事件的默认行为等等(在渲染进程无法做到)处理窗口的交互逻辑创建BrowserWindow,也就是渲染进程。合理设置窗口的参数,控制窗口的生命周期(例如何时销毁窗口),决定BrowserWindow加载何处的HTML与主进程通信,实现高级交互窗口、前端资源我们回顾一下刚刚讲到的执行流程,其中有一个有趣的点,就是Electron的窗口会加载一个HTML来渲染窗口的内容。 HTML,以及HTML加载的css、js文件,统称为前端资源如果不加载HTML的,客户端还能用吗?不妨来试试 // main process const win1 = new BrowserWindow(); const win2 = new BrowserWindow();上述代码在主进程中执行,创建了两个窗口,窗口并没加载HTML文件。但是窗口却是真实存在的,带有系统标准的控制栏,可拖动,是货真价实的系统窗口! 我们可以发现,前端资源和窗口是分离的。由主进程创建的的窗口(BrowserWindow),既是一个系统原生窗口,同时也是一个加载&渲染前端资源的容器 窗口通常会通过file协议和http(s)协议来加载前端资源,接下来我们看看这两种方式的区别。 通过file协议加载HTML在Electron的官方入门例子中,就是通过file协议来加载HTML的 通过file协议加载HTML,无论有没有网络,都可以加载到HTML文件,这是file协议核心优势。缺点也比较明显: 如果页面资源要更新,那么只能通过发版来解决(如果你用webview,那么webview的内容就可以自动更新,不过webview也需要有网络才能加载)在file协议下,无法通过ajax来请求数据(协议不同),只能通过NodeJs的http(s)模块来发起网络请求通过http协议加载HTML通过http协议加载HTML,优点是可以随时通过web页面的部署,更新渲染进程的资源,并且在https协议下,你可以在页面中使用前端熟悉的ajax请求来获取数据。 当然,缺点也比较明显: ...

June 13, 2019 · 1 min · jiezi

Electron酷家乐客户端开发实践分享-浏览器启动客户端

作者:钟离,酷家乐PC客户端负责人原文地址:https://webfe.kujiale.com/browser-to-client/酷家乐客户端:下载地址 https://www.kujiale.com/activity/136文章背景:在酷家乐客户端在V12改版成功后,我们积累了许多的宝贵的经验和最佳实践。前端社区里关于Electron知识相对较少,因此希望将这些内容以系列文章的形式分享出来。系列文章: 【Electron】酷家乐客户端开发实践分享 — 入坑篇【Electron】酷家乐客户端开发实践分享 — 软件自动更新【Electron】酷家乐客户端开发实践分享 — 浏览器启动客户端【Electron】酷家乐客户端开发实践分享 — 进程通信不定期更新...背景许多本地应用(例如vscode、QQ),都支持通过浏览器来启动PC上的本地软件 这个功能够使网页端和客户端联动起来,用户体验还是很好的,实现起来也并不复杂。酷家乐客户端已经支持了这个功能,如下图: 实现原理浏览器在解析url的时候,会尝试从系统本地寻找url协议所关联的应用,如果有关联的应用,则尝试打开这个应用 例如VsCode从web端安装插件的时候,实际上是访问了一个vscode协议的url,从而达到启动用户本地VsCode的目的 具体实现现在,我们只需要将自定义的协议注册到用户电脑上,就可以实现功能了。用户浏览器里访问带有自定义协议的url,即可启动我们的客户端。 Windows在windows下,注册一个协议比较简单,写注册表就可以了。这部分微软爸爸有很详细的文档,参考 Registering an Application to a URI Scheme) 建议在安装程序中写入注册表项,并且指定在卸载程序中,删除这些注册表项。以下是inno setup打包程序中,操作注册表的示例代码 [Registry]Root: HKCR; SubKey: Kujiale; ValueData: "KujialeProtocol"; ValueType: string; Flags: createvalueifdoesntexist uninsdeletekey;Root: HKCR; SubKey: Kujiale; ValueName: "URL Protocol"; ValueData: "{app}\{#appExe}"; ValueType: string; Flags: createvalueifdoesntexist uninsdeletekey;Root: HKCR; SubKey: Kujiale\DefaultIcon; ValueData: "{app}\{#appExe}"; ValueType: string; Flags: createvalueifdoesntexist uninsdeletekey;Root: HKCR; SubKey: Kujiale\shell\open\command; ValueData: "{app}\{#appExe} ""%1"""; Flags: createvalueifdoesntexist uninsdeletekey; ValueType: string;当然,也可以在软件启动的时候操作注册表,这个时候其实是用NodeJs来与注册表交互,推荐一个npm包node-regedit ...

June 13, 2019 · 1 min · jiezi

Electron酷家乐客户端开发实践分享-软件自动更新

作者:钟离,酷家乐PC客户端负责人原文地址:https://webfe.kujiale.com/electron-autoupdate/酷家乐客户端:下载地址 https://www.kujiale.com/activity/136文章背景:在酷家乐客户端在V12改版成功后,我们积累了许多的宝贵的经验和最佳实践。前端社区里关于Electron知识相对较少,因此希望将这些内容以系列文章的形式分享出来。系列文章: 【Electron】酷家乐客户端开发实践分享 — 入坑篇【Electron】酷家乐客户端开发实践分享 — 软件自动更新【Electron】酷家乐客户端开发实践分享 — 浏览器启动客户端【Electron】酷家乐客户端开发实践分享 — 进程通信不定期更新...更新原理在讲客户端更新方案之前,我们先了解一下web和客户端更新的原理 web应用在web应用的世界里,我们通常会更新web服务器上的前端代码(模板、HTML,也可能是js、css),来发布新的功能。在此之后用户再访问我们的web服务器,拿到的已经是更新过后的前端代码了。 web应用更新如此方便,得益于它中心化存储的方式: web应用的前端代码,一般集中储存在服务器或云服务上浏览器每次都会都会去服务器拉取最新的资源,用户本机实际上没有持久化储存web应用的代码浏览器缓存也算是在用户本机存储了前端代码,但是在web应用需要更新的时候,肯定是会禁用缓存的,否则这次发布对有缓存的用户无效。客户端和web应用的中心化储存不同,客户端的代码实际上是一种分布式存储,每个用户电脑上都有一份完整的代码文件,有点像git。 用户在电脑上安装客户端,实际上会将客户端代码文件持久储存到本机。例如在MacOS上,代码文件存放在/Applications目录下。 客户端内嵌web页面的更新方式,和上面讲到的web应用更新是一样的,不再赘述(参考移动APP内嵌的H5页面更新)结论web应用的更新,实际上是更新服务端代码文件 客户端的更新,实际上是更新用户电脑上代码文件 具体实现Electron官网有关于更新的教程 Updating Applications,但是都不能满足业务需求: update.electron.org,代码必须托管在github上,passelectron-builder,windows下只支持NSIS,而且需要搭建HTTP服务。更新程序UI和交互定制也不是很友好Deploying an Update Server,这个方案需要部署一个update server,也比较麻烦因此,我们使用的是自己实现的一套更新流程。 1、检查更新检查更新是整体流程的第一个步骤。如果有更新,后续的更新逻辑才会执行。通常我们会在软件启动时检查更新。 检查更新的策略,实际上是将本地客户端的版本与远程版本进行一次对比,然后根据版本对比的结果来给出不同的更新展示。 远程版本相比于自己搭建一个update server,维护一个远程的JSON数据成本是很低的。这个远程数据可以是一个后端接口或者cdn上的json文件,并且可以在需要更新的时候,及时更新远程数据的内容 这个远程JSON数据里面一般会存放版本号、更新内容介绍以及发布时间: const updateData = axios.get('https://some-update.json');console.log(updateData);/*{ version: '1.0.0', changeLogs: ['来个开发祭天','新增了????????的功能'], time: '2019-06-06',}*/本地版本在Electron中获取本地版本是非常简单的 app.getVersion const localVersion = app.getVersion(); // 0.0.1版本对比通常,远程版本号大于本地版本时,即认定为有更新。在有更新的情况下,我们还可以根据版本号里的major、minor、patch版本变动,来制定不同的更新策略。 // 远程版本 > 本地版本const shouldUpdate = semver.gt(removeVersion, localVersion);// 例子:major版本号变化时,给出强的更新提示。否则给出正常更新提示const isMajorUpdate = semver.diff(removeVersion, localVersion) === 'major';if (!shouldUpdate) return; // 无更新,不走后续if (isMajorUpdate) { console.log('给出强势更新')} else { console.log('给出普通的更新提示')}对于版本号的操作使用 semver2、更新提示检查到软件更新之后,需要给出更新提示来提醒到用户。此时,我们会使用一个窗口承载更新提示的内容,后面统称为更新窗口。 ...

June 13, 2019 · 1 min · jiezi

Electron酷家乐客户端开发实践分享-进程通信

作者:钟离,酷家乐PC客户端负责人原文地址:https://webfe.kujiale.com/electron-ku-jia-le-ke-hu-duan-kai-fa-shi-jian-fen-xiang-jin-cheng-tong-xin/酷家乐客户端:下载地址 https://www.kujiale.com/activity/136文章背景:在酷家乐客户端在V12改版成功后,我们积累了许多的宝贵的经验和最佳实践。前端社区里关于Electron知识相对较少,因此希望将这些内容以系列文章的形式分享出来。系列文章: 【Electron】酷家乐客户端开发实践分享 — 入坑篇【Electron】酷家乐客户端开发实践分享 — 软件自动更新【Electron】酷家乐客户端开发实践分享 — 浏览器启动客户端【Electron】酷家乐客户端开发实践分享 — 进程通信不定期更新...前言Electron中的进程,其实就是计算机中的进程,我们先来看看什么是进程通信 进程间通信(IPC,Inter-Process Communication),指至少两个进程或线程间传送数据或信号的一些技术或方法每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。 一个Electron应用有一个主进程和多个渲染进程,渲染进程还可能内嵌多个webview。两两之间都可能需要进行通信,情况还是比较复杂的。 需要通信的对象主进程: 使用ipcMain进行通信渲染进程: 使用ipcRenderer和remote模块进行通信webview: 一般会禁用webview的node集成,然后使用preload的方式拿到ipcRenderer来做进程通信。 // preload.jsconst electron = require('electron');const { ipcRenderer } = electron;// 把ipcRenderer挂载到window上,webview内部的js可以拿到这个模块window.ElectronIpcRenderer = ipcRenderer;ipcRenderer/ipcMain VS remote主进程和渲染进程通信方式,拧出来单独说一下。先来看一个简单例子的: 点击创建按钮,创建一个新的窗口。点击关闭按钮,关掉这个新窗口。 左侧代码使用ipcRenderer/ipcMain进行通信,右侧代码使用remote进行通信。实现的功能都是一样的。从这个例子中可以发现: 使用ipcMain/ipcRenderer通信,业务逻辑同时存在于主进程和渲染进程的代码中。同时为了通信,会产生非常多的event & event handler。使用remote通信,渲染进程直接获取主进程模块。而且,使用remote通信不需要使用事件和回调函数,写出来的代码清晰直观。主进程可以视作为模块提供者,而渲染进程是模块的消费者,渲染进程通过remote来获取主进程的模块,实现业务逻辑。这样做有以下好处: 主进程/渲染进程代码解耦,职责分明,提升可维护性业务逻辑内聚在渲染进程减少主进程/渲染进程冗余无用的代码具体实现介绍了一下前置知识,现在来看看不同情况下,Electron进程通信的实现方法。 主进程和渲染进程通信主进程发消息、渲染进程收消息:主进程使用窗口的webContents发消息,渲染进程内使用ipcRenderer收消息 // main.jsconst win = new BrowserWindow();win.load('index.html');win.webContents.send('hello', {a: 1});// index.html 中的jsconst { ipcRenderer } = require('electron');ipcRenderer.on('hello', (e, data) => { console.log(data); // 打印出 {a: 1}})渲染进程发消息、主进程收消息: 渲染进程使用ipcRenderer发消息,主进程使用ipcMain收消息。 ...

June 13, 2019 · 2 min · jiezi

用JS开发跨平台桌面应用从原理到实践

导读使用Electron开发客户端程序已经有一段时间了,整体感觉还是非常不错的,其中也遇到了一些坑点,本文是从【运行原理】到【实际应用】对Electron进行一次系统性的总结。【多图,长文预警~】 本文所有实例代码均在我的github electron-react上,结合代码阅读文章效果更佳。另外electron-react还可作为使用Electron + React + Mobx + Webpack 技术栈的脚手架工程。 一、桌面应用程序 桌面应用程序,又称为 GUI 程序(Graphical User Interface),但是和 GUI 程序也有一些区别。桌面应用程序 将 GUI 程序从GUI 具体为“桌面”,使冷冰冰的像块木头一样的电脑概念更具有 人性化,更生动和富有活力。我们电脑上使用的各种客户端程序都属于桌面应用程序,近年来WEB和移动端的兴起让桌面程序渐渐暗淡,但是在某些日常功能或者行业应用中桌面应用程序仍然是必不可少的。 传统的桌面应用开发方式,一般是下面两种: 1.1 原生开发直接将语言编译成可执行文件,直接调用系统API,完成UI绘制等。这类开发技术,有着较高的运行效率,但一般来说,开发速度较慢,技术要求较高,例如: 使用C++ / MFC开发Windows应用使用Objective-C开发MAC应用1.2 托管平台一开始就有本地开发和UI开发。一次编译后,得到中间文件,通过平台或虚机完成二次加载编译或解释运行。运行效率低于原生编译,但平台优化后,其效率也是比较可观的。就开发速度方面,比原生编译技术要快一些。例如: 使用C# / .NET Framework(只能开发Windows应用)Java / Swing不过,上面两种对前端开发人员太不友好了,基本是前端人员不会涉及的领域,但是在这个【大前端????】的时代,前端开发者正在想方设法涉足各个领域,使用WEB技术开发客户端的方式横空出世。 1.3 WEB开发使用WEB技术进行开发,利用浏览器引擎完成UI渲染,利用Node.js实现服务器端JS编程并可以调用系统API,可以把它想像成一个套了一个客户端外壳的WEB应用。 在界面上,WEB的强大生态为UI带来了无限可能,并且开发、维护成本相对较低,有WEB开发经验的前端开发者很容易上手进行开发。 本文就来着重介绍使用WEB技术开发客户端程序的技术之一【electron】 二、Electron Electron是由Github开发,用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库。 Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为Mac,Windows和Linux系统下的应用来实现这一目的。 2.1 使用Electron开发的理由:1.使用具有强大生态的Web技术进行开发,开发成本低,可扩展性强,更炫酷的UI2.跨平台,一套代码可打包为Windows、Linux、Mac三套软件,且编译快速3.可直接在现有Web应用上进行扩展,提供浏览器不具备的能力4.你是一个前端????????~当然,我们也要认清它的缺点:性能比原生桌面应用要低,最终打包后的应用比原生应用大很多。 2.2 开发体验兼容性 虽然你还在用WEB技术进行开发,但是你不用再考虑兼容性问题了,你只需要关心你当前使用Electron的版本对应Chrome的版本,一般情况下它已经足够新来让你使用最新的API和语法了,你还可以手动升级Chrome版本。同样的,你也不用考虑不同浏览器带的样式和代码兼容问题。 Node环境 这可能是很多前端开发者曾经梦想过的功能,在WEB界面中使用Node.js提供的强大API,这意味着你在WEB页面直接可以操作文件,调用系统API,甚至操作数据库。当然,除了完整的 Node API,你还可以使用额外的几十万个npm模块。 跨域 你可以直接使用Node提供的request模块进行网络请求,这意味着你无需再被跨域所困扰。 强大的扩展性 借助node-ffi,为应用程序提供强大的扩展性(后面的章节会详细介绍)。 2.3 谁在用Electron 现在市面上已经有非常多的应用在使用Electron进行开发了,包括我们熟悉的VS Code客户端、GitHub客户端、Atom客户端等等。印象很深的,去年迅雷在发布迅雷X10.1时的文案: 从迅雷X 10.1版本开始,我们采用Electron软件框架完全重写了迅雷主界面。使用新框架的迅雷X可以完美支持2K、4K等高清显示屏,界面中的文字渲染也更加清晰锐利。从技术层面来说,新框架的界面绘制、事件处理等方面比老框架更加灵活高效,因此界面的流畅度也显著优于老框架的迅雷。至于具体提升有多大?您一试便知。你可以打开VS Code,点击【帮助】【切换开发人员工具】来调试VS Code客户端的界面。 三、Electron运行原理 ...

June 10, 2019 · 8 min · jiezi

如何Electron中调用Dll

如何Electron中调用Dll客户端有些硬件的接口需要调试,是在电脑上连了一些硬件的设备,比如打印机、扫描仪或者进行串口通信等等。单靠JS是完成不了了,我们决定通过把C++或者C#把这些功能打包成Dll,然后在Electron客户端中通过Node调用Dll来实现所需要的功能。 Dll类型先简单说一下什么是Dll,Dll是动态链接库文件,也是一种代码库的形式,与静态链接库相比,它是在每次程序运行的时候去调用,而静态链接库指令都会被打包到最后的exe文件里,所以如果函数有什么变化那就需要重新生成exe,那动态链接库就不需要这么做了。生成Dll可以通过VS来完成,可以选择使用C#或者C++开发,C#开发界面的比较方便,如果你的功能需要弹出一些界面,那就要用C#编写相应的Dll。不过这里要注意了,用C#语言编写生成的Dll和用C++语言编写生成的Dll是不一样的,通过C#生成的Dll需要.net的开发环境,而C++生成的Dll就没有限制。 Node如何调用DllElectron里调用Dll其实就是node调用Dll,刚才说了,生成的Dll不一样,那么调用方式也不一样。我是用到了这两个模块,ffi和edge,使用ffi调用C++生成的Dll,使用edge调用C#生成的Dll。 ffi调用Dll比如我这里有个ffiTest.dll的文件,里面有个导出的函数叫做joinStr,就是暴露的方法,给定两个字符串,然后会返回这两个参数的拼接结果。注意C++生成的Dll要使用C风格extern “C”否则可能找不到对应的方法名。 var ffi = require('ffi');var path = require('path');var dllPath = path.resolve('ffiTest.dll');var lib = ffi.Library(dllPath, { 'joinStr': ['string', ['string', 'string']],})var result = lib.joinStr('hello', 'world');console.log(result); //打印 helloworld更详细的示例可以参考它的教程。ffi.Library里第二个参数是一个Json结构,key表示是方法名,value示一个数组,数组的第一个参数是返回值类型,第二个参数是方法的列表,如果返回值是空的话,那数组第一个参数应该是void。如果返回值或者参数类型不知道是什么类型就写void*。要使用ffi中的类型表示C/C++语言中的类型,对照表如下 基本类型int8 Signed 8-bit Integeruint8 Unsigned 8-bit Integerint16 Signed 16-bit Integeruint16 Unsigned 16-bit Integerint32 Signed 32-bit Integeruint32 Unsigned 32-bit Integerint64 Signed 64-bit Integeruint64 Unsigned 64-bit Integerfloat Single Precision Floating Point Number (float)double Double Precision Floating Point Number (double)pointer Pointer Typestring Null-Terminated String (char *)常见的C语言类型byte unsigned charchar charuchar unsigned charshort shortushort unsigned shortint intuint unsigned intlong longulong unsigned longlonglong longulonglong unsigned long longsize_t platform-dependent, usually pointer size如果是指针类型,可以利用ref模块来表示 ...

June 6, 2019 · 2 min · jiezi

使用ReactElectronDvaWebpackNodejsWebsocket快速构建跨平台应用

目前Electron在github上面的star量已经快要跟React-native一样多了这里吐槽下,webpack感觉每周都在偷偷更新,很糟心啊,还有Angular更新到了8,Vue马上又要出正式新版本了,5G今年就要商用,华为的系统也要出来了,RN还没有更新到正式的1版本,还有号称让前端开发者失业的技术flutter也在疯狂更新,前端真的是学不完的 回到正题,不能否认,现在的大前端,真的太牛了,PC端可以跨三种平台开发,移动端可以一次编写,生成各种小程序以及React-native应用,然后跑在ios和安卓以及网页中 , 这里不得不说-------京东的Taro框架 这些人 已经把Node.js和webpack用上了天对webpack不熟悉的,看我之前的文章 ,今天不把重点放在webpack 欢迎关注我的专栏 《前端进阶》 都是百星高赞文章 手写React优化版脚手架前端性能优化不完全手册手写vue脚手架本文源码git仓库地址先说说Electron官网介绍:使用 JavaScript, HTML 和 CSS 构建跨平台的桌面应用 ,如果你可以建一个网站,你就可以建一个桌面应用程序。 Electron 是一个使用 JavaScript, HTML 和 CSS 等 Web 技术创建原生程序的框架,它负责比较难搞的部分,你只需把精力放在你的应用的核心上即可。什么意思呢?Electron = Node.js + 谷歌浏览器 + 平常的JS代码生成的应用,最终打包成安装包,就是一个完整的应用Electron分两个进程,主进程负责比较难搞的那部分,渲染进程(平常的JS代码)部分,负责UI界面展示两个进程之间可以通过remote模块,以及IPCRender和IPCMain之间通信,前者类似于挂载在全局的属性上进行通信(很像最早的命名空间模块化方案),后者是基于发布订阅机制,自定义事件的监听和触发实现两个进程的通信。Electron相当于给React生成的单页面应用套了一层壳,如果涉及到文件操作这类的复杂功能,那么就要依靠Electron的主进程,因为主进程可以直接调用Node.js的API,还可以使用C++插件,这里Node.js的牛逼程度就凸显出来了,既可以写后台的CRUD,又可以做中间件,现在又可以写前端。谈谈技术选型使用React去做底层的UI绘制,大项目首选React+TS状态管理的最佳实践肯定不是Redux,目前首选dva,或者redux-saga。构建工具选择webpack,如果不会webpack真的很吃亏,会严重限制你的前端发展,所以建议好好学习Node.js和webpack选择了普通的Restful架构,而不是GraphQL,可能我对GraphQL理解不深,没有领悟到精髓在通信协议这块,选择了websoket和普通的http通信方式因为是demo,很多地方并没有细化,后期会针对electron出一个网易云音乐的开源项目,这是一定要做到的先开始正式的环境搭建 config文件放置webpack配置文件server文件夹放置Node.js的后端服务器代码src下放置源码main.js是Electron的入口文件json文件是脚本入口文件,也是包管理的文件~开发模式项目启动思路:先启动webpack将代码打包到内存中,实现热更新再启动Electron读取对应的url地址的文件内容,也实现热更新设置webpack入口 app: ['babel-polyfill', './src/index.js', './index.html'], vendor: ['react'] } 忽略Electron中的代码,不用webpack打包(因为Electron中有后台模块代码,打包就会报错)externals: [ (function () { var IGNORES = [ 'electron' ]; return function (context, request, callback) { if (IGNORES.indexOf(request) >= 0) { return callback(null, "require('" + request + "')"); } return callback(); }; })() ] 加入代码分割optimization: { runtimeChunk: true, splitChunks: { chunks: 'all' } },设置热更新等 plugins: [ new HtmlWebpackPlugin({ template: './index.html' }), new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin(), ], mode: 'development', devServer: { contentBase: '../build', open: true, port: 5000, hot: true },#### 加入babel ...

June 3, 2019 · 6 min · jiezi

使用Electronvue开发的客户端支付收款工具

Electron-vue开发的客户端支付收款工具目前实现了支付宝当面付的扫码支付功能、二维码支付功能,即主动扫和被动扫。测试请使用支付宝沙箱环境,支付宝是沙箱版。最终效果如下:前端页面使用阿里的组件,ant-design-vue通过node,使用nedb内存数据库进行本地数据存储安装文件支持自定义。生成的exe,安装过程如下 程序代码简述main.js import devtools from '@vue/devtools'import Vue from 'vue'import axios from 'axios'import App from './App'import router from './router'import store from './store'import db from './nedb'//订单表import Antd from 'ant-design-vue'import 'ant-design-vue/dist/antd.css'import alipayhelper from './alipayhelper'import moment from 'moment'//导入文件Vue.prototype.$moment = moment;//赋值使用Vue.prototype.$db = dbVue.prototype.alipayhelper = alipayhelper;Vue.use(Antd)if (!process.env.IS_WEB) Vue.use(require('vue-electron'))Vue.http = Vue.prototype.$http = axiosVue.config.productionTip = false/* eslint-disable no-new */new Vue({ components: { App }, router, store, template: '<App/>'}).$mount('#app')alipayhelper.js 里存储的支付宝收款方的APPID,pem路径下应用私钥。这些信息可以通过阿里官方申请,即可以在线收款 const path = require('path');const fs = require('fs');const moment = require('moment');const crypto = require('crypto');const electron = require('electron');const dataPath = (electron.app || electron.remote.app).getPath('userData');const home = (electron.app || electron.remote.app).getPath('home');const appData = (electron.app || electron.remote.app).getPath('appData');let ALI_PAY_SETTINGS = { APP_ID: '2016100100638328', APP_GATEWAY_URL: 'http://localhost',//用于接收支付宝异步通知 AUTH_REDIRECT_URL: 'xxxxxxx',//第三方授权或用户信息授权后回调地址。授权链接中配置的redirect_uri的值必须与此值保持一致。 //__dirname 获取当前目录,无法在生产模式assr 获取到路径 /* APP_PRIVATE_KEY_PATH: path.join(__dirname, 'pem', 'rsa_private_key.pem'),//应用私钥 APP_PUBLIC_KEY_PATH: path.join(__dirname, 'pem', 'rsa_public_key.pem'),//应用公钥 ALI_PUBLIC_KEY_PATH: path.join(__dirname, 'pem','ali_rsa_public_key.pem'),//阿里公钥*/ APP_PRIVATE_KEY_PATH: path.join(__static, '/pem/rsa_private_key.pem'),//应用私钥 APP_PUBLIC_KEY_PATH: path.join(__static, '/pem/rsa_public_key.pem'),//应用公钥 ALI_PUBLIC_KEY_PATH: path.join(__static, '/pem/ali_rsa_public_key.pem'),//阿里公钥 AES_PATH: path.join(__dirname, 'pem', 'remind', 'sandbox', 'aes.txt'),//aes加密(暂未使用) ALI_GATEWAY_URL: 'https://openapi.alipaydev.com/gateway.do?',//用于接收支付宝异步通知};

May 23, 2019 · 1 min · jiezi

基于-Electron-React-的超高颜值喜马拉雅客户端-Mob-诞生记

前言最近一个月沉迷喜马拉雅无法自拔,听相声、段子、每日新闻,还有英语听力,摸鱼学习两不误。上班时候苦于没有桌面端,用网页版有些 bug,官方也不搞一个,只好自己动手了。样式参考了一下 Moon FM /t/555343,颜值还过得去,自我感觉挺好 ???????????? 简介Mob(モブ), 异能超能 100的男一号。 GitHub: zenghongtu/Mob基于 Electron, Umi, Dva, Antd 构建 功能及 UI 目前实现的功能有这些: 一个基本的音乐播放器每日必听推荐排行榜分类订阅听过下载声音搜索专辑技术选型技术栈: ElectronUmiDvaAntd之所以选择 Umi 是因为在之前项目中研究过其部分源码,开发体验感不错,而且 bug 也少。还有一个原因是我在找模板的过程中,看到这个大佬的模板wangtianlun/umi-electron-typescript,就直接拿来用了,大大减少了我搭建开发环境的时间,在此表示感谢~ 如果你对 Umi 和 Dva 不熟,墙裂建议去学一下,分分钟就可以上手,而且开发效率要提高的不要太多。开发篇React Hooks 使用问题在开发中,所有组件、页面都是使用 React Hooks 进行开发的。而让我觉得最难以琢磨的一个 hooks 非 useEffect 莫属。 // ...useEffect(() => { ipcRenderer.on("HOTKEY", handleGlobalShortcut); ipcRenderer.on("DOWNLOAD", handleDownloadStatus); return () => { ipcRenderer.removeListener("HOTKEY", handleGlobalShortcut); ipcRenderer.removeListener("DOWNLOAD", handleDownloadStatus); };}, [volume]);// ...const handleGlobalShortcut = (e, hotkey) => { switch (hotkey) { case "nextTrack": handleNext(); break; case "prevTrack": handlePrev(); break; case "volumeUp": const volumeUp = volume > 0.95 ? 1 : volume + 0.05; handleVolume(volumeUp * 100); break; case "volumeDown": const volumeDown = volume < 0.05 ? 0 : volume - 0.05; handleVolume(volumeDown * 100); break; case "changePlayState": handlePlayPause(); break; default: break; }};// ...为了减少渲染次数,我会设置了第二参数为 [volume],但这会导致一些出乎意料的情况,比如我触发了changePlayState,但却并没有得到意料中的值,这个时候设置为 [volume, playState] 就正常了。 ...

May 14, 2019 · 4 min · jiezi

Electron快速入门

开篇词:这是一篇快速实现 Hello World 并打包发布的教程。我们跳过吹捧它,因为你点开这篇文章,肯定你已经被 Electron 迷住了。写这篇文章,我设想您是遇到了下面的问题:想一次学习搭建开发环境、创建项目、编译打包成 exe 文件、想 Git 做版本管理、想 SemVer 做发布版本管理,而官方或者其他渠道找到的资料都是需要拼凑的。尝试了几次,被各种拦路虎挡住,一个 Hello World 都没弄出来。您将学到的知识点:搭建开发环境国内高速镜像安装Electron-forge并自动创建项目手工创建 Demo 项目处理 loadFile...... is not function报错编译打包成 exe Git 做开发版本管理SemVer 做发布版本管理一、简介Electron 通俗点讲就是用 HTML、CSS、JavaScript 开发跨平台桌面应用。专业介绍请看官网:https://electronjs.org/ ,但是不建议去看了,我想大家搜到 Electron 这个关键词,已经是在其他渠道被它迷住了,这里是直接来收干货的。提示:以下操作都是在 Windows10 系统上操作的,执行命令除了说明在 Visual Studio Code (没有 VSCode的先安装:https://code.visualstudio.com/)的终端工具上执行的外,也可以是在操作系统自带的 CMD 命令行工具上执行的,但是编程 IDE 如果您有其他,也可以。二、安装安装 Node.js下载并安装 Node.js(先确定要装哪个版本的 Electron,然后下载对应的 Node.js 版本,Windows 系统下载后直接下一步下一步安装即可,npm 包管理器会一起安装,npm 能从网上一个存软件包的仓库下载文件,简单高效,下面将用到它)。安装 Electron用 npm 包管理器安装 Electron (怕一夜都没装起来的朋友,请自动忽略这一步,跳到淘宝镜像那一步):npm install -g electron用淘宝镜像提速:npm install -g cnpm --registry=https://registry.npm.taobao.orgcnpm install -g electron-g 是全局安装,如果执行上一步提示了 npm 版本过低,用下面的代码升级。npm install -g npmPS:如果想不同项目使用不同版本的 Electron,则代码如下 ...

May 6, 2019 · 2 min · jiezi

electron实现开机自启+通过连接调起应用

实现原理通过修改注册表来实现开机自启和通过连接调起应用(类似百度网盘点击连接打开应用程序)使用到的插件node-regedit yarn add regedit使用说明将下方例子中的electronApp替换为自己的应用别名(可以不为exe名称)开机自启const regedit = require(‘regedit’);const { app } = require(’electron’);const path = require(‘path’);// 开机自启动// 查看开机自启注册表是否已经注册electronAppregedit.list(‘HKCU\Software\Microsoft\Windows\CurrentVersion\Run’, (err, data) => { if (err) { console.log(err) } if (!data[‘HKCU\Software\Microsoft\Windows\CurrentVersion\Run’].values.electronApp || data[‘HKCU\Software\Microsoft\Windows\CurrentVersion\Run’].values.electronApp !== app.getPath(’exe’)) { // 未注册或注册地址与现地址不一致则 进行注册 regedit.putValue({ ‘HKCU\Software\Microsoft\Windows\CurrentVersion\Run’: { ’electronApp’: { value: app.getPath(’exe’), type: ‘REG_SZ’ } } }, (error) => { if (error) console.log(error) }) }});通过连接调起应用 const regedit = require(‘regedit’); const { app } = require(’electron’); /** * 下文中的electronAPP替换为自己需要的唤起名 * 使用时直接在html中使用<a href=‘electronAPP://’>唤起app</a> */ let url = app.getPath(’exe’); // 获取可运行exe存放目录 function setPath (url) { regedit.putValue({ ‘HKLM\SOFTWARE\Classes\electronAPP’: { // 设置注册表url调用electronApp ‘defaule’: { value: ’electronAPP’, // 设置点击url的弹出框名字(表现不好) type: ‘REG_DEFAULT’ }, ‘URL Protocol’: { value: ‘’, type: ‘REG_SZ’ }, ‘path’: { value: ${url}, type: ‘REG_SZ’ } }, ‘HKLM\SOFTWARE\Classes\electronAPP\shell\open\command’: { ‘defaule’: { value: "${url}" "$1", // 需要唤起的应用程序路劲 type: ‘REG_DEFAULT’ } } }, (putErr) => { console.log(putErr) }) } if (url) { // 判断启动url是否正确(用户重新安装,并将安装目录修改) regedit.list(‘HKLM\SOFTWARE\Classes\electronAPP’, (listErr, docData) => { if (listErr) { regedit.createKey([‘HKLM\SOFTWARE\Classes\electronAPP\shell\open\command’], (createErr) => { if (!createErr) { setPath(url) } }) } else { if (docData[‘HKLM\SOFTWARE\Classes\electronAPP’].values.path.value !== url) { setPath(url) } } }) } ...

April 17, 2019 · 1 min · jiezi

electron 获取打包后的exe文件路径。

储存应用数据时,通常会使用 应用程序所在目录。即 userData 目录。路径是这样的:C:\Users\【用户名】\AppData\Roaming\【应用名】可通过以下方法获取:app.getAppPath()但某些情景。我希望某些数据存放在 打包后的当前路径 下。即 应用名.exe 的同级目录下。这时该怎么获取呢?1、初步尝试使用 nodeJS 的被执行 js 文件的绝对路径:__dirname。 返回: D:\【文件夹】\win-ia32-unpacked\resources\app.asar\dist\electron使用 electron 文档中提到的:“当前应用程序所在目录”:app.getAppPath()。 返回: D:\【文件夹】\win-ia32-unpacked\resources\app.asar都不是想要的结果。2、找到答案经过搜索,我找到了一个贴子:https://stackoverflow.com/que…使用 process.execPath 即可获取: D:\【文件夹】\build\win-ia32-unpacked\vsqx.exe使用 process.cwd() 即可获取: D:\【文件夹】\build\win-ia32-unpacked

April 16, 2019 · 1 min · jiezi

mac下npm全局安装electron出现的权限问题解决方法

网络问题Error: connect ETIMEDOUT 54.231.81.176:443….用淘镜像源cnpm命令安装npm install cnpm -g –registry=http://registry.npm.taobao.org然后再用命令cnpm install electron -g不过我试了不管用然后我就继续找问题原因试了试另外一个命令,不报网络错误,开始报权限错误了https://npm.taobao.org/mirrors/ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ npm install -g electron2. 权限问题sudo npm install -g electronError: EACCES: permission denied, mkdir ‘/usr/local/lib/node_modules/electron/electron-tmp-download-1374-1511880539207’npm ERR! code ELIFECYCLEnpm ERR! errno 1npm ERR! electron@1.7.9 postinstall: node install.jsnpm ERR! Exit status 1npm ERR!……查了查度娘,给了几个命令,试了试还是没什么用rm -rf ~/.electronmkdir ~/.electronchmod 777 ~/.electron/sudo npm -g install electron后来发现一个mac权限帖子,解决了这个问题,我的mac系统版本是macOS High Sierra 10.13.6对于Mac OS X 10.11 El Capitan用户,由于系统启用了SIP(System Integrity Protection), 导致root用户也没有权限修改/usr/bin目录。按如下方式可恢复权限。屏蔽方法:重启Mac,按住command+R,进入recovery模式。选择打开Utilities下的终端,输入:csrutil disable并回车,然后正常重启Mac即可。具体可见:http://www.howtogeek.com/2304…

April 13, 2019 · 1 min · jiezi

基于Electron开发Hosts切换工具的“踩坑”之旅

用过好几个Hosts切换工具,但总是有点这样那样的问题。最讨厌的莫过于切换完后,键盘都快按坏了,浏览器里面的Hosts就是不变,网上找了好多方法,但是感觉都并不完美,于是就有了这篇文章Electron说起桌面应用,以前一直想开发个跨平台的应用,学习了一下Qt,偷了一些QQ的素材,整了个简单的IM,但是迫于C++基本属于语法入门阶段,写个东西是真费劲。。。最近几年Electron忽然火了起来,也诞生了很多好用的应用。Electron本身是基于Chromium和Node.js,让你可以使用 HTML, CSS 和 JavaScript 构建应用。简单来说就是浏览器的壳子,里面套HTML页面,但是拥有了浏览器之外的能力。作为PHP程序员,肯定会点JavaScript,也可以假装会点Node.js吧使用了各种第三方库electron-vueElectron文档大概看了一下,不知道如何下手,然后找到了electron-vue,几行命令就可以帮你把项目初始化好了Element-UI饿了么出品,基于Vue的UI组件库CodeMirror代码编辑器插件神器,提供超级超级超级多的功能和配置chrome-remote-interfaceChrome调试协议的第三方调试客户端实现,为node程序提供了api,想要在Chrome中实时生效的关键工具就是它lowdb名字虽然low,但是很好用。一个本地储存的JSON数据库(其实就是自己懒得操作JSON。。。)坑来了上面写了一大坨,基本全是介绍,下面才是我在开发过程中,遇到的问题,其实大部分都是自己学艺不精,没有想清楚造成的主进程 && 渲染进程Electron 应用同时使用了 main(主进程) 和一个或者多个 renderer(渲染进程) 来运行多个程序开发的时候并没有想太多,等某些功能实现的时候懵逼了,比如托盘的菜单(main)在切换Hosts的时候,页面(renderer)中的勾选状态需要同步更新。这个时候才看文档,发现人家提供了进程间的ipc通信方法,造成了代码逻辑的大量改动和结构优化,如果你的应用也有类似功能,开发之前一定要想清楚各种事件消息如何相互传递并且妥善管理。不同的进程从数据库中获取到的数据不一致看了lowdb半天文档,没整明白。不就是读文件么,都写成功(同步方式)了,别的进程就是读不出来呢?初步怀疑,数据在内存中存了一份儿(同步机制不详)。忽然发现lowdb的一句介绍提到了Lodash(我并不知道这是啥),看了一下才明白const db = low(adapter)返回的是一个Lodash实例,只有当触发write()和read()的时候才真正的去操作文件,单进程的话没什么问题,但是多进程就好操作数据不一致了,解决方法就是在读取的时候先read()一下:db.read().get(‘hosts’),搞定。目录不存在当你兴奋的把应用传给同事,结果人家一打开,一个大大的报错(一脸懵逼+尴尬),原来是app.getPath(‘userData’)获取到的目录默认并不存在,在操作前加目录判断,没有就创建。程序打包后,不能复制粘贴网上查了一下,发现Mac程序内如果需要复制粘贴,需要加到菜单中,代码如下:if (process.platform === ‘darwin’) { const template = [ { label: ‘我的应用名称’, submenu: [ { label: ‘退出’, accelerator: ‘Command+Q’, click: () => { app.quit() } } ] }, { label: ‘编辑’, submenu: [ { label: ‘复制’, accelerator: ‘CmdOrCtrl+C’, selector: ‘copy:’ }, { label: ‘粘贴’, accelerator: ‘CmdOrCtrl+V’, selector: ‘paste:’ } ] } ] Menu.setApplicationMenu(Menu.buildFromTemplate(template))}

April 13, 2019 · 1 min · jiezi

electron 改变窗体大小

相关链接: electron-vue 集成 element-ui 在开发 electron 的时候遇到了需要在 render 中修改 BrowserWindow 窗口大小的方式,经过一番尝试,有两种方法实现:通过 ipcRenderer 与 ipcMain 的通讯来实现通过 render 的 remote 模块来实现ipcRenderer 和 ipcMain 实现实现原理是 render 进程通过 ipcRenderer 与 ipcMain 进行通讯以通知 main 进程操作窗体操作。 在 render 引入 ipcRendererlet {ipcRenderer} = require(’electron’)发送同步消息给 main 进程ipcRenderer.sendSync(‘synchronous-message’,’logined’)在 main 中监听同步消息,并处理 logined 消息操作ipcMain.on(‘synchronous-message’, (event, arg) => { if (arg === ’logined’) { mainWindow.resize(1000, 1000) }})remote 方式是实现引入 remote 模块const { remote } = require(’electron’)调用 remote 方法中的 getCurrentWindow 获取当前窗体对象,然后进行修改窗体属性remote.getCurrentWindow().setSize(1000, 1000)总结上面实现方式可以看出 remote 方式其实是比较简单和方便的,我个人更倾向于用第二种方式实现此功能。其实在 remote 模块的底层实现也是通过发布同步消息的方式来实现与 main 进程通讯的,本质上和我们实现的方式一是一样的,既然 eletron 已经做了一个很好的封装,完全也没有必要舍近求远 直接用 remote 方式实现是一个比较优雅的方式。参考链接electron remote electron ipcMain electron ipcRenderer ...

April 13, 2019 · 1 min · jiezi

electron-vue 集成 element-ui

简介什么是 electronElectron 是由 Github 开发,用 HTML,CSS 和 JavaScript 来构建跨平台桌面应用程序的一个开源库。 Electron 通过将 Chromium 和 Node.js 合并到同一个运行时环境中,并将其打包为 Mac ,Windows 和 Linux 系统下的应用来实现这一目的。Electron 于 2013 年作为构建 Github 上可编程的文本编辑器 Atom 的框架而被开发出来。这两个项目在2014春季开源。目前它已成为开源开发者、初创企业和老牌公司常用的开发工具什么是 electron-vueelectron-vue 是为了要避免使用 vue 手动建立起 electron 应用程序。electron-vue 充分利用 vue-cli 作为脚手架工具,加上拥有 vue-loader 的 webpack、electron-packager 或是 electron-builder,以及一些最常用的插件,如vue-router、vuex 等等什么是 elecment uiElement UI 是一套采用 Vue 2.0 作为基础框架实现的组件库,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的组件库,提供了配套设计资源,帮助你的网站快速成型安装 electron-vue# 安装 vue-cli 和 脚手架样板代码npm install -g vue-cli //如果你已经安装忽略此处vue init simulatedgreg/electron-vue my-project# 安装依赖并运行你的程序cd my-projectyarn # 或者 npm installyarn run dev # 或者 npm run dev运行结果如下: 安装 element-uinpm i element-ui -S在 /my-project/src/renderer/main.js 中引入 element ui// element-uiimport ElementUI from ’element-ui’import ’element-ui/lib/theme-chalk/index.css’Vue.use(ElementUI)此时就可以在任意 .vue 文件中添加和使用 element-ui 元素了。<el-button @click=“message” type=“success” icon=“el-icon-search” round>默认按钮</el-button>运行效果如下: 总结整个安装过程并不复杂,为什么是 electron 以我目前的技术栈来讲 electron 是最容易上手的开发桌面程序的一个途径,尽管他也有这样或那样的缺点,但总归来说还是瑕不掩瑜,此番安装成功,接下来就开始了 electron 开发之旅参考链接electron-vue 文档 element ui博客地址 ...

April 13, 2019 · 1 min · jiezi

初识Electron

之前有个需求,说是可能会用到electron,于是稍微了解了一下,顺便做个记录。对于初学者而言,我最关心的往往不是这个框架的内核,而是我多久能打出HelloWord,能不能满足业务需求,我投入的时间与我收获的东西等不等值(当然这一点毋庸置疑是不等值的????)?虽然对electron什么也不懂,没关系,上来就是萌新三连先百度一哈看看。&01.啥是electron?感谢seo优化逻辑,百度第一行这串字使用 JavaScript, HTML 和 CSS 构建跨平台的桌面应用就很好的解释了啥是electron。什么?你还不懂?好吧,我也不懂但我看着解释一哈。桌面应用就是针对于pc端的各种系统(win/mac/linux)的应用程序。就好像开发移动端的小程序一样,用前端语言模拟开发Android/ios应用,electron能用前端语言开发pc端的应用。electron作为一种为前端开发者(或说是js/html/css语言的使用者)所开发的一种能用前端语言生成桌面应用的框架。你只需要要和往常一样编写你的前端代码,诸如布局样式,页面交互,数据处理,请求响应等等。而当你编写完成,下达了打包的命令后,electron会帮你生成对应的桌面应用。也就是说,如果没什么细节的配置要求,你只管写你的前端代码,怎么生成桌面应用就是一句命令的事。啥是electron?electron是个框架,给它个命令,它能生成桌面应用,Over.&02.为啥用electron?贴一下之前整理的几点笔记:简要Electron 跨系统 开发框架Electron 通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为 Mac ,Windows 和 Linux 系统下的应用来实现这一目的。同时也支持跨平台开发,诸如移动端APP,小程序,H5等,但适应性不及原生的跨平台开发框架(Weex,DCloud…)框架特性开源客户端vs浏览器端 (安全性,用户体验,本地工具…)适应多系统的运行环境适应系统下原生的通讯联动(系统提示/任务栏常驻/崩溃监控/开机自启…)个人信息、操作日志的本地化数据库存储适配部分 chrome 的 crx 扩展插件打包文件大(几十兆,至少包含一个浏览器程序大小),吃内存 ## 开发 ##在 /src/renderer/ 渲染进程内 进行 vue 项目开发;vue配套的工具插件(axios,vuex,router…)照常使用一个主进程与多个渲染进程 模拟 多页面/多弹窗 的情景Electron 版本发布相当频繁Electron 不支持 Web Worker 多进程,使用 background process & 缓存机制不同系统间存在部分api差异自动更新 autoUpdater win&mac , 不支持LinuxNeDB 嵌入式持久数据库 & 浏览器端缓存机制使用fs操作系统文件为啥用electron?不需要额外的编写,把原来写好的前端代码搬到electron对应渲染目录下即可,快速构建一个桌面应用,还能同时适配(并非百分百适配)三个操作系统,是网页服务端向PC应用端过渡的一个不错方案,Over.&03.怎么用electron?emm…那我要怎么用呢?如果仅仅是知道了啥是electron,那想真正玩起来还是蛮麻烦的,你可能还会遇到不少问题,还是要看不少博客文章,当然这都是正常的。打开官网,快速浏览下网站,你会发现官网有一个快速入门的demo。我下过来看了一哈,感觉就真的是一个入门demo,没啥特殊的。反倒是怎么把入门demo跑起来倒花了我点时间,因为electron的下载可能需要外网资源,所以会遇到安装失败的情况,解决办法就是使用cnpm淘宝镜像源。//npm安装electron要从国外下载一个45MB左右的zip包,//特别的慢(8KB…),所以我们可以采用下面的这条安装命令。//条安装命令会从淘宝的镜像站下载这个zip包,速度很快。 $ npm install electron -g –registry=http://registry.npm.taobao.org// 当然你也可以使用cnpm来全局安装electron $ npm install cnpm -g –registry=http://registry.npm.taobao.org $ cnpm install electron -g你可以用electron白手起家,跟着demo自己边学边造一个工程出来;也可以在demo的基础上改改接着开发;或者呢找找看有没有脚手架之类的。因为我之前是学习的vue,所以看到 vue-cli + electron 的关键字后就感到特别亲切。主要依赖&版本"electron": “^2.0.6”, // latest 4.0.3| beta 5.0.0 | nightly 6.0.0, “vue”: “^2.5.16”,“vue-cli”: “^2.9.6”,“vue-electron”: “^1.0.6”, “vue-router”: “^3.0.1”, “vuex”: “^3.0.1”, “vuex-electron”: “^1.0.0”, “iview”: “^3.2.2”, “axios”: “^0.18.0"起步 P.S. vue-cli 为 2.0 版本# 安装 vue-cli 和 脚手架样板代码 # 此模板依赖的 electron 默认为”^2.0.6",需求高版本的可自行更改npm install -g vue-cli vue init simulatedgreg/electron-vue my-project # install dependenciesnpm install# serve with hot reload at localhost:9080npm run dev# build electron application for productionnpm run build# pack electron applicationnpm run pack开发在/src/renderer/ 目录下 进行 vue 项目开发打包结果会在build文件夹(还是dist来着具体我忘了)生成几个exe安装程序。把exe文件复制到桌面,安装并打开即可。怎么用electron? 使用vue-cli脚手架傻瓜式创建一个项目出来,并在/src/renderer/ 目录下 进行 vue 项目开发,开发之前先看看Electron|API里有没有贴合业务需求的,有就尽量拿来用,没有再自己写,Over.&04.EX-electron结语即使我们什么都不写,一个空的electron工程打包完也有20mb左右的大小,我猜这是其内部浏览器的大小。我们甚至可以说electron就是一个内部封装了一整个chrome的工程。它之所以能适配端系统,就是因为在三个系统上使用的都是同一个chrome浏览器的内核。我们的electron开发视图也都是在这个内置的浏览器中显示,而不是我们常用的外部真实的浏览器,而且在这个内部浏览器中你也能使用开发者模式进行调试,使用chrome的大部分功能,同时又多了许多与操作系统交互的api。相当于本来写的项目需要做不同浏览器适配,现在用electron就可以只写chrome的适配(不用IE!!!)。然后原来是 直接在不定浏览器中访问的方式改成在一个提供且仅提供chrome浏览器功能的程序中访问业务。当然,做窗口分辨率适配还是要的。你可以用它快速地搭建自己的桌面小应用。最后,我们的需求是既要网页应用也可能要桌面应用,和上级沟通了一哈,选择了下面的第三种方案A。方案C├─── vue本地开发=>vue生产发版=>网页应用│ ├── vue迭代版本 ↑│ ├── vue外套electron ↓└─── electron生产发版=>桌面应用** 一次迭代更新vue&electron,两个都要重新发版静态文件。方案B├─── vue本地开发=>vue生产发版=>网页应用=>close│ ├── vue迭代版本 ↑ │ ├── vue整体搬迁electron ↓│ ├── electron内部开发 ↓└─── electron生产发版=>桌面应用=>桌面&网页应用** 一次迭代更新electron,需要时间搬迁原工程。方案A├─── vue本地开发=>vue生产发版=>网页应用│ └───vue迭代版本 ↑ └─── electron 近空项目生产发版=>桌面应用**[打开程序后仅提供vue网页工程的网址访问,类似electron打开一个百度页面的程序窗口]** 一次迭代更新vue,Over.End,因为对electron的业务需优先级不高,所以这个需求也先搁置下来了。我也没做更深入的研究,初识electron就到这了。以后有时间的话可以用这个做个小程序玩玩。参考链接安装 | ElectronElectron | API-目录简介-electron-vueelectron 两种打包electron入门心得electron学习历程electron+webpack2+多入口配置electron去边框&可拖动electron打开新窗口::: ...

April 7, 2019 · 1 min · jiezi

electron 文件及文件夹上传的问题探索

不晓得现在electron最新版的dialog解决了这个问题没有 选择文件夹的时候没有返回文件夹里面所有的文件 而是返回了文件夹的路径 同样的在网页端是可以通过webkitDirectory获取到文件夹下面所有的文件我试了很多的方式 下面的方式是可以成功上传文件夹下面所有的文件(这里单文件的上传不在叙述 只讨论文件夹的上传)本人业务需要 添加了一些你可能不用的参数 所以逻辑是这个 具体就看你自己的做法第一 通过dialog获取到文件夹的路径 定义为dirpath.第二 通过node的fs读取文件夹下的所有文件路径 代码大概如下:function fileDisplay(dirpath){ fs.readdir(dirpath,(err,files)=>{ if(err){console.log(err)}else{ files.forEach((filename)=>{ var filedir = path.join(paths,filename); fs.stat(filedir,(error,stats)=>{ if(error){}else{ var isFile = stats.isFile(); var isDir = stats.isDirectory(); if(ifFile){ fs.readFile(fileDir,(eror,data)=>{ if(err) throw err; filepack(data,filedir) }) } if(ifDir){ fileDisplay(fileDir) } } }) }) } })} 第三 生成文件function filepack(data,filepath){ let = filename = filepath.split(’\’)[filepath.split(’\).length-1]; let file = new File([data],filename,{type:‘image/’+filepath.split(’.’).reverse()[0]}); let fileData = new FormData(); fileData.append(‘file’,file); fileData.append(‘filename’,filename); let fileUploadData = fileData.getAll(‘file’)[0]; post(fileUploadData)}function post(file){ //提交 ajax.post()}第三步中的 new File那步中我直接用image是可以上传任意其他格式的文件的 目前没有发现会受这个的影响 有更好的方式的朋友可以给我说哈 再次说明 直接用代码肯定是不行的 因为去掉了很多中间的不重要的步骤 但是基本的三个是上面的几个上传的代码参考iview upload组件的源码 我是通过修改它的源码来改进的我是参考这个搞出来的:https://blog.csdn.net/Wbiokr/… ...

April 1, 2019 · 1 min · jiezi

electron-vue开发音乐播放器 之 窗口的mini模式

electron-vue 窗口的mini模式electron中打开新窗口通常就是新开一个html页面,vue是单页面,只有一个入口文件,它的路由跳转是H5的location;设想新增入口文件,就是新加html,但这样失去了单页面的作用,vue也无法在两个不同html中运行方法我个人的方法是子窗口打开同一个html文件,但路由地址不同为什么要这样,直接拖曳缩小不行吗?不行为了布局这些,设置了窗口最小大小,设置了以后,窗口的setSize方法中宽高小于最小宽高是无效的,mini化也就只能新建窗口了实现// main/index.jsfunction createWindow () { // 初始化窗口就是mainWindow,省略 BrowserWindow.mainWindow = mainW; // 一定要加,用于判断是否新建子窗口,否则会不断新建 BrowserWindow.miniWindow = null;}// 监听窗口状态ipc.on(‘winreduction’, (event, state) => { // 监听从mini窗口变回原来大小 if(state == ‘mini’) { // 主要是这里,发送一个窗口要从mini模式还原到原来大小,事件从mini.vue发出 event.sender.send(‘full’, ‘reduction’) } else { mainWindow.unmaximize(); } event.sender.send(‘statechange’, ‘reduction’);})// app.vue// 在appvue里新建子窗口const electron = require(“electron”);const ipc = electron.ipcRenderer;const currWin = electron.remote.getCurrentWindow();import { setTimeout, clearTimeout, setImmediate } from “timers”;let miniWindow = null;function createMiniWin() { let miniWindow = new electron.remote.BrowserWindow({ width: 280, height: 48, frame: false, titleBarStyle: “hidden”, // macOs useContentSize: true, show: false, center: true, resizable: false, webPreferences: { webSecurity: false // 禁用同源策略 } }); let winURL = window.location.href; let uri = winURL + winURL.substring(0, winURL.indexOf("#") + 1); if (/#/.test(uri)) { uri += “mini”; } else { uri += “#/mini”; } miniWindow.loadURL(uri); miniWindow.once(“ready-to-show”, () => { miniWindow.hide(); // mainWindow.setAlwaysOnTop(true, ‘status’); // 总是最顶层 if (miniWindow.moveTop) { miniWindow.moveTop(); } }); miniWindow.on(“closed”, () => { miniWindow = null; }); return miniWindow;}// 这就是在index里设置BrowserWindow.miniWindow的原因,这样就会只创建一次if (electron.remote.BrowserWindow.miniWindow === null) { miniWindow = electron.remote.BrowserWindow.miniWindow = createMiniWin(); ipc.send(“miniCreate”, miniWindow);}export default { name: ‘app’, data() { return { state: “reduction”, } }, mounted() { ipc.on(“statechange”, (event, data) => { this.state = data; mainWindow: electron.remote.BrowserWindow.mainWindow, miniWindow: electron.remote.BrowserWindow.miniWindow, }); ipc.on(“full”, (event, state) => { this.state = state; /* 这个是一个坑,本来我设想是路由跳转回来,但是会导致窗口出现延迟,就像是打开网页一样的感觉 因为是边学边用,没有看过全部API,我找了半天,终于找到一个goBack方法,用于回退,没有延迟 感觉和原生一样 */ this.mainWindow.webContents.goBack(); // mini隐藏,main显示 this.miniWindow.hide(); this.mainWindow.show(); }); }, // 省略 watch: { state(old, news) { if (old == “mini”) { // 点击缩小按钮,路由跳转到mini,并且将主窗口隐藏,mini窗口显示 this.$router.push({ name: “mini” }); this.miniShow(); } } }}// mini.vueconst electron = require(“electron”);const remote = electron.remote;const ipc = electron.ipcRenderer;export default { name: “mini”, data() { return { mainWindow: remote.BrowserWindow.mainWindow, }; }, created() {}, methods: { close() { ipc.send(“winclose”); }, full() { ipc.send(“winreduction”, “mini”); // 发出要从mini窗口变回原来大小 } }};效果如图mini还没画预告一下网上的electron实现播放器,我找了半天也没有如何mini化的,花了不少时间接下来是另一个问题,读取本地的文件夹,就是上图打开本地文件那个如何读取音乐并获取它的时长,信息,作者,专辑呢,我利用了ffmpeg这个模块但随之而来的是ffmpeg必须要安装才能使用,我们最终打包不同客户端,不可能强制客户安装其它软件才能用我们的应用,网上也没什么解决实例,我不会c,c++什么的具体方法下一篇 ...

April 1, 2019 · 2 min · jiezi

Electron 跨平台应用开发入门

本文由 Deguang 发表于 码路-技术博客Tips:Electron 介绍Electron 环境搭建进程通信调用系统 APIWrite once, run anywhere. Sun 公司 Java 介绍词。端的跨平台实现方案有哪些?Web(浏览器)移动端设备:Hybrid(混合)、React Native、Weex、Flutter桌面端:NW.js、Electron、Flutter(~1.0)Electron1. 什么是 ElectronElectron 是由 Github 开发,用 HTML,CSS 和 JavaScript 来构建跨平台桌面应用程序的一个开源库。 Electron 通过将 Chromium 和 Node.js 合并到同一个运行时环境中,并将其打包为 Mac,Windows 和 Linux 系统下的应用来实现这一目的。开发者只要使用 Web 技术完成业务部分即可,这就是对前端开发者最友好的地方。2. Electron 应用结构Elctron 应用运行时,分为 主进程 和 渲染进程。主进程 (Main Process)package.json 中定义的 main 脚本运行的进程,被定义为 主进程,一个 Electron 应用有且只有一个主进程。主进程可以创建 Web 页面窗口,并传入 URL 加载网页作为图形界面。渲染进程 (Renderer Process)Electron 的每个页面都有它自己的进程,叫做渲染进程。每一个渲染进程都是独立的,只关心它所运行的页面;进程通信(IPC, Inter-Process Comminication)Electron 不允许渲染进程调用系统 GUI 的原生 API,例如打开文件选择之类的系统操作,这种对系统 API 的调用只允许存在与主进程中。渲染进程,也就是页面调用 系统 API,需要由主进程担任桥梁的作用,来完成操作并得到返回结果。这里有两种方式可以实现进程通信:使用 ipcRenderer 和 ipcMain 模块通信ipcRendereripcRenderer 是从渲染进程到主进程的异步通信,可以使用它提供的一些方法从渲染进程发送同步或异步的消息ipcMainipcMain 是从主进程到渲染进程的异步通信,处理从渲染进程发送出来的异步和同步信息,// renderer processconst {ipcRenderer} = requier(’electron’)// asyncipcRenderer.on(‘PICK_FILE_CALLBACK’, (event, arg) => { console.log(arg) // files})ipcRenderer.send(‘PICK_FILE’)// syncipcRenderer.sendSync(‘PICK_FILE_SYNC’) // files// main processconst {ipcMain} = require(’electron’)// asyncipcMain.on(‘PICK_FILE’, (event, data) => { // do something … event.sender.send(‘PICK_FILE_CALLBACK’, ‘files’)})// syncipcMain.on(‘PICK_FILE_SYNC’, (event, data) => { // do something event.returnValue = ‘files’})more: ipcRenderer、 ipcMainipcMain、ipcRenderer 又是什么?ipcMain 和 ipcRenderer 都是 EventEmitter 类的一个实例。 EventEmitter 类是由 NodeJS 中的 events 模块定义、导出的。EventEmitter 类是 NodeJS 事件的基础,实现了事件模型需要的接口, 包括 addListener,removeListener, emit 及其它工具方法. 同原生 JavaScript 事件类似, 采用了发布/订阅(观察者)的方式, 使用内部 _events 列表来记录注册的事件处理器。使用 remote 进行 RPC 通信在渲染进程中使用主系统模块。// 渲染进程中const { BrowserWindow } = require(’electron’).remotelet win = new BrowserWindow({width: 600, height: 400})win.loadURL(‘https://github.com’)remote 对象remote 模块返回的对象表示主进程中的一个对象,调用远程对象、远程函数时,相当于发送同步进程信息。let win = new BrowserWindow({width: 600, height: 400}),相当于在主进程中创建了一个 BrowserWindow 对象,然后在渲染进程中返回了相应的远程对象。more: remote3. APIElectron APIElectron 提供了大量 API 以优化桌面应用开发体验。Electron API 都有指派进程类型(文档中标记):只能用于主进程(BrowserWindow)、只能用于渲染进程(remote) 和 两种进程中均可使用。Node.js APIElectron 同时在主进程和渲染进程中对Node.js 暴露了所有的接口原生 API// Native APIconst fs = require(‘fs’)fs.readFile(…)第三方 Node.js 模块npm install -save axiosconst axios = require(‘axios’)axios.interceptors.request(…)4. 第一个 Electron 应用环境依赖:Node.js && Npm (Yarn)Node.js: 安装参考 https://nodejs.orgYarn: npm i -g yarn推荐使用 yarn 构建 Electron,使用 npm 大概率出现依赖安装失败的情况起步0.0.1 创建 demo 目录,执行 npm init,配置启动脚本 “start”: “electron .”// package.json{ “name”: “demo”, “version”: “1.0.0”, “description”: “”, “main”: “main.js”, “scripts”: { “start”: “electron .” }, “author”: “”, “license”: “ISC”}0.0.2 创建 main.js && index.html➜ demo tree.├── index.html├── main.js└── package.json0.0.3 安装 Electronnpm install –save-dev electron0.0.4 创建一个窗口:// main.jsconst { app, BrowserWindow } = require(’electron’)let winfunction createWindow() { win = new BrowserWindow({ width: 600, height: 400}) win.loadFile(‘index.html’)}app.on(‘ready’, createWindow)<!– index.html –><body> <h1>Hello, Electron.</h1> Node.js: <script>document.write(process.versions.node)</script>; Chrome: <script>document.write(process.versions.chrome)</script>; Electron: <script>document.write(process.versions.electron)</script>.</body>0.0.5 执行 npm run start,即可打开一个窗口,显示 Hello, Electron.。0.0.6 更新main.js,尝试更多功能:// main.jsconst { app, BrowserWindow } = require(’electron’)let winfunction createWindow() { win = new BrowserWindow({ width: 600, height: 400}) win.loadFile(‘index.html’) win.webContents.openDevTools() win.on(‘close’, () => { // 关闭窗口、清空 win 对象 win = null }) app.on(‘window-all-closed’, () => { // macOS 应用通常关闭窗口还是保活的,只有使用 cmd + q 强制退出 if (process.platform !== ‘darwin’) { app.quit() } }) app.on(‘activate’, () => { // macOS 点击 dock 图标并且没有其他窗口打开时,重新创建一个窗口 if (win === null) { createWindow() } })}app.on(‘ready’, createWindow)0.0.7 重新执行 npm run start 启动应用。more: 更多 Electron API 使用,可以尝试官方 electron-api-demos打包应用electron-packager# 安装 electron-packagernpm install electron-packager –save-dev# 基本命令electron-packager <sourcedir> <appname> –platform=<platform> –arch=<arch> [optional flags…]# 例如:# electron-packager ./ demo –out ./dist –app-version 1.0.0 –overwrite参数说明:sourcedir:项目路径appname:应用名称platform:构建平台的应用(Windows、Mac 还是 Linux)arch:x86、 x64 还是两个架构都用optional options:可选选项为了更方便的构建,在 package.json 增加构建脚本"package": “electron-packager ./ demo –out ./dist –app-version 1.0.0 –overwrite"执行 npm run packagemore: electron-packagerelectron-builder# 安装 electron-buildernpm install electron-builder –save-dev// package.json 增加如下配置"build”: { “productName”: “xxx”, “appId”: “com.xxx.xxx”},“scripts”: { “dist”: “electron-builder”}执行 npm run dist,完成打包more: electron-builderps:mac 跨平台 打包 win32 需要 wine客户端数据库Electron 应用可以在本地使用客户端数据库,来维护本地数据,进行相应的 CURD 操作,下面是常用的一些轻量级数据库:nedbsqliteRxdb…参考文档:Electron 文档 ...

March 29, 2019 · 3 min · jiezi

electron打包踩过的坑总结

vue-electron 执行npm run build时,在build的时候会因为下载远程打包所需文件而超时,然后根据错误一步一步就行手动安装相应的文件。虽然在网上参考了很多相关方法,最终还是失败,然后屡次尝试后,终于成功了。附上elelctron相关的淘宝镜像地址:https://npm.taobao.org/mirror…step1:npm run build后,第一次报错需要下载 electron-v2.0.18-win32-x64.zip(我这里是需要该版本的文件,根据自己的错误信息,来选择对应的版本下载即可),在镜像中选取该版本号 2.0.18,点击进入,并选择下载 electron-v2.0.18-win32-x64.zip 和 SHASUMS256.txt, 下载完成后,将SHASUMS256.txt文件改成 SHASUMS256.txt-2.0.18,然后将两个文件拷入如图位置:step2:完成step1后,继续npm run build,发现又有文件下载失败 winCodeSign-2.4.0(我这里是需要该版本的文件,根据自己的错误信息,来选择对应的版本下载即可),然后自己手动下载https://github.com/electron-u…,这里下载的是Source code(zip),速度快,下载完成后解压,拷贝如图位置所有文件:, 拷贝至如图位置:step3:完成step2后,继续npm run build,发现又有文件下载失败 nsis-3.0.3.2(同上),然后自己手动下载https://github.com/electron-u…,同上,下载完成后解压,拷贝如图位置所有文件:, 拷贝至如图位置:step4:完成step3后,继续npm run build,发现又有文件下载失败 nsis-resources-3.3.0,但是按照上面的方法操作,最后还是会报错,然后我尝试,用step3中下载解压后的这个nsis-3.0.3.2版本试试,拷贝如图位置所有文件: 拷贝至如图位置:至此,我们一共进行了四次拷贝操作,完成以上四步操作后,运行npm run build,不一会儿就能打包成功,得到你的第一个exe版本。

March 17, 2019 · 1 min · jiezi

如何使用前端技术开发一个桌面跨端应用

本文将会讲述一个完整的跨端桌面应用 代码画板 的构建,会涉及到整个软件开发流程,从开始的设计、编码、到最后产品成型、包装等。本文不仅仅是一篇技术方面的专业文章,更会有很多产品方面的设计思想和将技术转换成生产力的思考,我将结合我自己的使用场景完全的讲解整个开发流程,当然涉及到设计方面的不一定具有普遍实用性,多数情况下都是我自己的一些喜好,我只关心自己的需求。同时本文只从整体上讲思路,也会有个别的技术细节和常规套路,有兴趣的也可以直接去 github 上看 源码,文章会比较长,如果你只想知道一些拿来即用的「干货」,或许这篇文章并不是一个好的选择一、定位需求事情的起因是这样的,因为我们内部会有一些培训会议。会经常现场演示一些代码片段。比如说我们讲到 React 的时候会现场写一些组件,让大家能直观的感受到 React 的一些功能。但是通常由于条件所所限,会议总会遇到一些意外。比如断网、投影分辨率低看不清文字等起初我们用的是在线版的 codepen,但是感觉并不是那么好用。比如不能方便的修改字体大小,必须要在连网的情况下才能使用。另外它的 UI 设计不是很紧凑,通常我们展示代码的时候都投影是寸土寸金的,应该有一个简洁又不失功能的 UI 界面,能全屏展示…于是我解决自己实现一个这样的轮子,那么大概的需求目标是有了:离线可用可以改变界面字体大小更加简洁的 UI…二、整体设计应用风格代码画板解决的是 临时性 的一些 演示代码 的需求,所以它的本质属性是一个拿来即用的工具,它不应该有更复杂的功能,比如用户登录、代码片段的管理等。这些需求不是它要解决的。代码画板会提供一个简单的导出成 HTML 文件的功能,可以方便用户存储整个 HTML 文件。既然是用来演示代码的,那么它的界面上应该只有两个东西,一个是 代码,一个就是 预览。像代码/控制台切换的功能都做成 tab 的形式,正常情况不需要让他们展示出来。像 codepen 那样把所有的代码编辑器功能都展示出来我认为是不对的。codepen 的界面给人感觉非常复杂,有很多功能点。当然我并不是在批评它,codepen 做为一个需要商业化运营的软件,势必会做的非常复杂,这样才能满足更多用户的需求。然而程序员写软件则可以完全按照自己的想法来,哪怕这个应用只给自己一个人用呢。桌面应用的设计桌面应用的设计和 web 界面的设计还是有些细微区别的,同样的基于 electron 的应用,有的应用会让人感觉很「原生」,有的则一眼就能看出来是用 CSS 画的。我在设计代码画板的时候也尽量向原生靠近,避免产生落差感。比如禁用鼠标手型图标、在按钮或者非可选元素上禁止用户选择:cursor: default;user-select: none因为实际上用户在使用一款应用的时候感性的因素影响占很大一部分,比如说有人不喜欢 electron 可能就是因为看到过 electron 里面嵌一个完整的 web 页面的操作,这就让人很反感。但是这不是 electron 的问题,而是应用设计者的问题。应用标识的设计说实话应用 logo 设计我也是业余水平,但是聊胜于无。既然水平不行,那就尽量设计的不难看就行了。可以参考一些好的设计。我用 sketch 画出 logo 的外形,sketch 有很多 macOS 的模块可以从网上下载下来,直接基于模板修改就可以了。代码画板主要的界面是分割开的两个面板,左边是代码,右边是预览。所以我就大概画了一个形状这个 logo 有个问题就是线条过多,小尺寸的时候看不清楚。这个问题我暂时先忽略了,毕竟我还不是专业的,后续有好的创意可以再改默认设置代码画板也 不会有 设置界面,因为常用的设置都预定义好了,你不需要配置。顶多改变下代码字体的大小。使用编辑器的通用快捷键 command++/- 就解决了,或者插入三方库,直接使用编辑器的通用命令快捷键 command+p 调出。我们的思路就是把复杂的东西帮用户隐藏在后台,观众只需要关注演员台上的一分钟,而不必了解其它细节。快捷键/可用性由于代码画板的界面非常简单,在一些细小的必要功能就得添加一些快捷键。比如:切换 HTML/CSS/JS/Console 代码编辑器,我在每个 tab 上加了数字标号,暗示它是有顺序有快捷键的,而且这个切换方式和 Chrome tab 切换的逻辑一致,使用 command+数字 就可以实现,万一还是有人不会用的话,可以去看帮助文档。里面有所有的快捷键。界面中间的分割条可以自定义拖动,双击重置平分界面刚开始的时候我把每个 tab 页签都分割成单独的面板,因为我觉得这个能拖动自定义面板大小的交互实在是太爽了,忍不住想去拖动它。但是后来想想,其实并没有必要,我们写代码时应该更专注于代码本身,如果只有两个面板,那么这个界面无论是认知还是使用起来就没有任何困难。因为我们并不需要把一堆的功能的界面摔给用户,让他们自己去选择。三、技术调研实现控制台通过使用流行的几款在线代码运行工具,我发现他们有一个共同的问题:控制台很难用。无法像 Chrome Console 那样展示任意类型的 JS 值。比如我想 log 一段嵌套的 JS 对象:console.log({ a: { b: 1, c: { d: [1, 2, 3] } }})大多数都展示成这样的:[object Object] { a: [object Object] { b: 1 }}Chrome 是这样的:显然 Chrome 控制台中更直观。所以我们需要在前面的基础上加一个需求,即:实现一个基于 DOM 的日志展示界面(无限级联选择)日志界面应该有下面这些功能:展示任意 JS 类型的数据Primitive 类型的数据显示不同的颜色(number - 蓝色,string - 绿色)Object 类型默认折叠起来,点击按钮展示子级,属性过多需要展示缩略信息数组前应该有长度标记能展示 JS 运行时的报错 Error 信息集成现代化的前端框工作流现代化的前端写页面肯定不是 HTML/CSS/JS 一把梭了,至少应该有 Sass/Babel 的支持吧。Sass 嵌套能让你少写很多选择器,当然 Less 也可以,但是在我们的这个应用里面区别不大,一般来说临时性的写一些代码很少会用到它们的细节功能。有 变量 和 选择器 嵌套就够了Babel 主要是解决了写 React 的问题,不用再安装一大堆的构建工具了,直接使用 UMD 的 React/ReactDOM 就可以了,而且 electron 内嵌的 chromium 也支持了 es6 的 class 写法,实际上 Babel 主要的目的还是用来转译 JSX注意这里是有一个我认为是 刚性 的需求,比如临时忽然有个想法,或者想验证一段代码的话,正常情况是使用你的编辑器,新建 demo.html/demo.css/demo.js 等这些操作。但是这些动作太浪费时间了。有了代码画板以后,直接打应用就可以开始 coding 了,真正能做到开箱即用。提高程序的扩展性我们在写 demo 页面时通常是要引用很多第三方类库的,比如:Bootstrp/jQuery 等。我希望有一种方法可以方便的引用到这些库,直接把库文件的 link/script 标签插入到代码画板的 HTML 中,但是前端框架真的是太多了,又不能一个个去扣来写死到页面,就算是写死了随着框架版本的升级,可能就无法满足我们的需求。以前写页面时经常会用到 bootcdn,无意中发现它提供了相关 API,可以直接拿来使用。接下来就得想办法让用户通过界面选择即可。这个 API 有三层数据结构:库 - 版本 - 资源链接。这个功能要用界面来实现肯定会非常臃肿,界面上可能会放很多按钮。这就违背了「更简洁」的需求目标。这时就得参考下我们经常使用的一些软件是如何解决 简洁性 和 功能性 需求之间的矛盾问题的,我比较喜欢 Sublime Text 的一些界面设计,Command Palette 是我经常使用的,所以我决定再模拟一个 Command Palette 来实现插入第三方库的需求。而且重要的是这个 Command Palette 并不一定只用来实现这一个功能,或者后期会有一些别的功能需要添加,那这个 Command Palette 也是个很好的入口。使用 electron 实现桌面应用实现离线可用很多方法,比如使用 PWA 技术。但是 PWA 并不能给我带来一种原生应用的那种可靠感,相反 electron 刚好可以解决我的顾虑。同时它可以把你的应用打包成各个平台(macOS/Window/Linux)的原生应用。唯一的缺点就是安装包确实很大,一般来讲一个 electron 应用 安装完 至少要 100 多兆,不过我觉得还能接受,毕竟硬盘存储现在已经很廉价了。有人可能对 electron 有抗拒,觉得 electron 应用太庞大、占系统资源什么的,不过我们做的这个应用并不需要常驻系统,临时性的使用一下,用完就关闭,正常写生产环境的代码肯定还是要换回 编辑器/IDE 的。同时因为 electron 降低了写桌面应用的门槛,确实有很多人把一个完整的在线的网页直接嵌进去,这也是有问题的。electron 还有一个好处,因为它完全基于 HTML/CSS/JS 来实现 UI(可以使用 Chrome only 的一些新功能),那我们理论上可以在做桌面应用时顺手把 web 应用也做了。这就可以同时支持各个系统下的原生应用,并且有 web 在线版本。如果你不愿意使用原生应用,直接登录 web.code-sketch.com 使用在线版也没是一种选择。这样就使得我们的应用具有真正的 跨端 能力。由于我们团队都使用了 macbook,所以我优先支持 macOS 的开发,另外 macOS Mojave 的系统级别的暗色主题我也比较喜欢,刚好实现支持 mojave 暗色主题这个需求也做上。三、框架的选择大方向确定了,像框架选择这个就简单了,基于 electron 的应用,需要你区分开 render/main process 来选择。Render process渲染进程 就是 electron 中界面的实现部分 ,一般来说就是一个 webview,选自己喜欢的框架即可。我使用 React 来实现界面。样式方面就不再使用框架了,因为我们的界面原则上没有复杂的元素,直接手写 CSS,300 行内基本上就可以解决问题。可能有人会觉得这不可能,实际情况是当你写样式只跑在 Chrome 里面的时候那感觉完全爽到飞起,CSS variable/flex/grid/calc/vh/rem 什么的都可以拿来用,实现一个功能的成本就降低了很多。我使用 Codemirror 来做为主界面的代码编辑器,Monaco 也是一个好选择,但是它有点过于庞大了,而且如果想要自定义功能得自己写很多实现主界面上的分割组件,使用了 React-split。Main process主进程 就是 electron 应用程序的进程,主要的区别在于主进程中可以调用一些与原生操作系统交互的 API,比如对话框、系统风格主题等。并且有 node 的运行时,可以引用 NPM 包。当然渲染进程也可以有 node 支持,但是我建议渲染进程中就只放一些纯前端的逻辑,这样的话方便后期把应用分离成 web 版因为我们要集成 Sass 编译功能,如果你也经历过 node-sass 的各种问题,那就应该果断选择 dart-sass — 使用 dart 实现,编译成了原生的 JS,没有依赖问题。dart-sass 我放在了 main process 中,因为我试过放在 render process 中会有各种报错。如果 web 端要实现这个功能就需要其它的解决办法了,比如做成一个 http 服务,让 web 调 http 服务。Babel 的话我是放在了 渲染进程 中以 script 标签的方式调用,这样即使在 web 端 Babel 编译也是可用的。总之如果你使用 electron 构建应用并且引入的第三方 NPM 包可以 支持 运行在客户端(浏览器)上,那就尽量把包放在渲染进程里面。构建工具我使用 Parcel 来构建 React 而不是 Create React App。后者用来写个小应用还可以,稍微大一点的,需要定制化一些东西你就得 eject 出来一大堆 webpack 配置文件,即便是我已经用 webpack 开发过几个项目了,但是说实话我还是没用会 webpack。写 webpack 配置的时间足够我自己写 npm script 来满足自己的需求了。原生应用打使用 electron-builder 来打包到平台原生应用,并且如果你有 Apple 开发者账号的话应用还可以提交到 AppStore 上去。我目前的打包参数是这么配置的:{ “build”: { “productName”: “Code Sketch”, “extends”: null, “directories”: { “output”: “release” }, “files”: [ “icon.icns”, “main.js”, “src/*.js”, “所有需要的文件”, “package.json”, “node_modules/@babel”, “node_modules/sass” ], “mac”: { “icon”: “icon.icns”, “category”: “public.app-category.productivity”, “target”: [ “dmg” ] } }}在你的 package.json 中添加 build 字段,productName, directories 这些按自己需要更改即可四、分离开发环境区分开开发环境代码画板项目开过过程中涉及两个关键环境Parcel 构建环境(渲染进程):Parcel 可以为你提供一些现在 JS 的转译工作,因此你可以放心使用例如 ES6 的 JS 新特性Node.JS 运行环境(主进程+渲染进程):这个取决于你的 electron 版本中集成的是 node 版本,比如:Node 10 中就没有 ES Module,这意味着你如果要在 electron 主进程 是无法识别 import 这样的语句的,但是渲染进程由于你使用了 Parcel 编译,则无需考虑这里温馨提示下:想要做到 electron 中的 渲染进程与主进程之间共享 JS 代码是非常困难的。就算是有办法也会特别的别扭,我的建议是尽量分离这两个进程中的代码,主进程主要做一些系统级别的 API 调用、事件分发等,业务逻辑尽量放在渲染进程中去做如果非要共享,那建议单独做成一个 NPM 包分别做为主进程运行时依赖,和渲染进程的 Parcel 编译依赖,唯一的缺点就是实际上共享的代码会有两份。渲染进程中调用 node API 可能会和 Parcel 打包工具冲突,一般在调用比如文件模块时,可以加上 window.require(‘fs’) 这样就可以兼容两个环境:get ipc() { if (window.require) { return window.require(’electron’).ipcRenderer } else { return { on() {}, send() {}, sendToHost() {} } }}this.ipc.send(’event’, data)这样的话你在浏览器端调试也不会产生报错。一般情况下,建议当你用渲染进程中的 JS 引用(require)包的时候都加上 window. 前缀就可以了。因为渲染进程中 window 是全局变量,调用 require 和调用 window.require 是等价的开发流程通常在测试的时候应用会调用一些 electron 内置的系统级别 API,这部分调用通常需要启动 electron,但是有时候只有渲染进程中 UI 界面上的改动,就不用再启动 electron 了,直接在浏览器里面测试即可。使用 Parcel 运行一个本地的服务,这样就可以在浏览器里面调试页面。整个开发过程需要两个命令(NPM Script):启动 Parcel 编译服务器"scripts": { “start”: “./node_modules/.bin/parcel index.html -p 2044”}调试 electron 原生功能,注意设置 ELECTRON_START_URL"scripts": { “dev”: “ELECTRON_START_URL=http://localhost:2044 yarn electron”,}技术难点整个应用只有两个功能是需要我们自己写代码实现的:日志控制台,Sublime 命令行。我们分别来分析下这两个模块的难点。日志控制台 的难点在于,我们需要打印任意类型的 JS 值。如果你对 JS 了解比较多的话自然会想到在 JS 中所有的东西都是 对象,即 Object,那么实际上当你想打印一个变量的时候,其实你只要把整个 Object 递归的遍历出来,然后做成一个无限级的下拉菜单就可以了。看起来大概想下面这样:Sublime 命令行 实际上开发起来还是比较简单的,使用 React 很简单就实现了功能,比较麻烦的是调用 bootcdn 的接口,过程中我发现接口返回数据量还是挺大的,有必要做上一层 localStorage 缓存,加快二次打开速度。然而在使用的过程中你会发现当我想插入一个前端库需要很多操作,因为有 三级选择:库-版本-CDN 链接。虽然这个流程解决了 所有用户 的使用问题,但是却损害了 大部分 用户的体验。这个时候插入一个常用库的成本就很高了,所以我们就要加上一些快捷入口,来实现一键插入流行框架。我们写代码的思路是满足所有用户的使用需求,但是一个好产品的思路是先满足大多数用户(80%)的常规需求,再让其余的用户(20%)可以有选择还有一个问题比较典型就是 React 这类框架在渲染大列表并且进行过滤(关键字查询)时性能的问题。注意这个性能问题 并不是 引入框架产生的,真正的原因是当你渲染的 HTML 节点数以千计的时候,批量操作 DOM 会使得 DOM Render 特别慢。所以说当我们遇到性能问题的时候应该去查找问题的根源,而不是停留在框架使用上,实际上在 DOM 操作这个层面来讲 jQuery 提供了更多的性能优化,比如自身的缓存系统,以致于当你在使用的时候很难发现有性能问题。但是在类 React 框架中它们框架本身的重点并不在于解决你应用的性能问题。类似我们上面讲到的,实际上 jQuery 帮助你屏蔽了很多舞台背后的东西,以致于你可以不用操心技术细节,你甚至可以把 jQuery 当做一个 产品 来使用,而类 React 框架你却要亲力亲为的用他来设计你的代码。话题再转回性能问题。这时候需要我们去实现一个类似于 react-window 的功能,让列表元素根据滚动按需加载。这可能是一种通用的解决大列表加载的方案,但是我的解决方法更粗暴,因为我们的下拉过滤功能使用时用户只关注 最佳的匹配项 即可,后面匹配程度不高的项可以直接限制数量裁剪就行了嘛。很少有用户会一直滚动到下面去查找某个选项,如果有,那就说明我们这个匹配做的有问题。slice() { const idx = (this.props.itemsPerPage || 50) * (this.state.activeFrame + 1) return this.props.items.slice(0, idx)}整个匹配筛选的状态大概是这样的:this.state = { // 当前第N步选择 step: 0, // 当前步骤数据 items: [], // 是否显示 active: false, // 当前选中项 current: {}, // 过滤关键字 keyword: ‘’}这个 items 是当前步骤的所有数据,实际上我们这个组件是支持无限级的扩展的,那么我们通过组件的 props 传入所有层级的数据,然后持久存储在内存中。这个 所有层级的数据 是数据结构层面的,实际上它可能是通过异步接口获取的。再来看看我们组件提供的所有 props:static defaultProps = { step: 0, active: false, data: [[]], // 无限层级数据 [[], [], [], …] // 数据的主键,用于钩子函数返回用户选择的结果集 pk: ‘id’, autoFocus: true, activeCls: ‘active’, delay: 300, defaultSelected: 0, placeholder: ‘’, async: false, alias: [], done: () => {}}这些数据都可以通过组件的 props 传入,这就意味着我们的这个组件才是真正的组件,别人也可以使用这样的功能,而他们并不用在意里面的细节,使用者只需要做好类似调用自己接口的这种业务逻辑。组件的调用大概是这样的:<CommandPalette step={0} key=“CommandPalette” async={injectData} done={this.done.bind(this)} alias={alias} aliasClick={this.aliasClick.bind(this)} data={[ [], [], [] ]}async 这个 props 实际上是一个异步调用的钩子方法,它会回传给你组件上当前操作的相关数据状态,通过这些数据使用者就可以按自己的需求在不同的步骤上调用不同的方法export const injectData = (step, item, results, cb) => { const API = ‘https://api.bootcdn.cn/libraries' if (step === 0) { fetchData(${API}.min.json) .then(processLibraryData) .then(cb) } else if (step === 1) { // … } else if (step === 2) { // … }}另外关于 React 这里安利下自己翻译过的一个教程:React 模式,里面讲到 18 种短小精悍的 React 模式案例,非常简单易懂。还有一个小窍门,我们在适配暗色主题时,传统的方法是直接写两套主题 CSS 代码,实际上我们要使用 CSS Variable 的话完全没必要生成两套了,背景色,字体都做成 CSS 变量,切换的时候只需要动态往页面插入更新过的 CSS 变量值即可系统的一些参数想直接传给渲染进程也是比较麻烦的,我的做法是直接从主进程中的 loadUrl 方法上以 queryString 的方式传到渲染页面的 URL 上const query = { theme: osTheme, app_path: app.getAppPath(), home_dir: app.getPath(‘home’)}mainWindow.loadURL(process.env.ELECTRON_START_URL ? url.format({ slashes: true, protocol: ‘http:’, hostname: ’localhost’, port: 2044, query}) : url.format({ slashes: true, protocol: ‘file:’, pathname: path.resolve(app.getAppPath(), ‘./dist/index.html’), query}))像程序运行时的一些参数(比如程序的根目录)也可以这么动态传过去,而且还有一个好处就是你甚至可以在渲染进程中测试与这些参数相关的功能。五、宣传demo 视频录制我会把最终所有功能的使用方法录制成一个视频,万一有人不不想下载你的软件,只是要了解一下,这就是个很好的方法。我同时上传到了 Youtube 和 bilibili 这两个平台,其它的都有广告就没必要了使用 Quicktime Player 即可,录制完使用 iMovie 转码成两倍速率的 mp4。如果你有兴趣还可以加上一段音乐什么的,让视频看起来更灵动域名申请域名是一个能让用户记住你产品的方法,如果你做的是一个成型的产品,那就一定要申请个域名。我总是有这样的体验,有的时候看到一个非常不错的产品但由于当时没需求就忽略了,想起来或者突然有需求的时候缺记不起来名字叫什么了。事实上代码画板最开始我给他起的名字是 code playground,这个更直观,但是名字太长,而且想用到的一些域名呀、Github 名、NPM 包都被注册了。想来想去就换成了 code sketch,这和符合我们的设计初衷,即:一边是代码,一边是效果/草图域名申请我一般会上 Godaddy,不用备案,.com 域名一年 ¥65.00,然后 DNS 服务器转到了 cloudflare,后续域名也会直接转到 cloudflare。因为据说以后在 cloudflare 上续费域名最便宜网站搭建宣传网站直接放在 github pages 上,做个自定义域即可,实在是太方便了。而且还有 SSL 支持,Github 真的是业界良心web 版的代码画板,由于我们把渲染进程中的代码分离开发,所以直接把 parcel 打包出来的静态文件也做成 github pages 就可以了,爽歪歪,网站就等于一分钱不花了。后续做一些 web 版的增强功能时,可以做成前后端分离的 http 服务,这就是后话了加入 Google analytics 代码GA 可以让你了解网站的用户分布情况,清楚的知道网站访问的波动。比如说你把自己的链接放到某个网站上分享了,GA 里面就能看出来所有的推荐来源和波动,对于运营来说是非常有必要的广告语这个我还真想了好长时间,基于我对于代码画板的定义,我觉得它应该是一个我们有一个想法的时候需要快速去实现一个 demo 的地方,想来想去就定了一段看起来文邹邹的话,虽然听名字根本不知道它是干啥用的,但是没关系,程序员写东西就是要有个性,因为我的受众只有自己。First place where the code was written…一个你最初写代码的地方…六、汇总使用到的库与工具麻雀虽小,五脏俱全。我们来看下代码画板总共用到了多少东西:框架/库electronjsreactbabeljsNPM 模块codemirror 及其插件react-splitsublime-command-palette打包/工具parceljselectron-builderbootcdn设计与素材sketchFree AppIcon GeneratorInconsolata 字体Gallary CSS 纯 CSS 实现的焦点图,用于宣传页展示七、结语总结实事上我自己的开发这个应用的时候并没有严格按照这篇文章的顺序执行,而是想到一些实现一些,可能一个功能实现了后来觉得不好又干掉了,是不断的取舍、提炼的结果。开发中我也不断的问自己这个功能是否有必要,如果可有可无那是不是可以去掉,这样才能使得用户更加关注于代码本身。整个开发过程中自己实现的功能模块并不多,只有控制台、命令行窗口是自己实现的,其它的功能基本上都是靠社区现有的工具库来完成的,从这一点来说前端技术的生态还是挺好的。这使得当我从整体上构思一个产品时我不必在意那些细节,虽然过程中还是能感觉到前端工具/库的割裂感,但是整体而言还是向好的,毕竟工具对于开发者只是一种选择的。八、引用https://github.com/keelii/code-sketchhttp://www.tweaknow.com/appicongenerator.phphttp://benschwarz.github.io/gallery-css/https://addyosmani.com/blog/react-window/https://github.com/keelii/reactpatterns.cn原文:https://mp.weixin.qq.com/s/Lbnx0aYdRa5iDhOkCgEWjg ...

March 14, 2019 · 4 min · jiezi

Electron学习笔记:渲染进程向主进程注册回调的坑

以下代码会出现错误// 主进程const el = require(’electron’);el.app.testMain = { testName: ‘main process object’, testCallback: function(callback) { callback.apply(this); }};// 渲染进程const mt = require(’electron’).remote.app.testMain;mt.testCallback(function() { console.log(this.testName);});渲染进程的回调方法在主进程中调用时,被封装成一个名为callInRenderer的方法,因此使用apply绑定this时,并没有实际绑定到渲染进程的回调方法上面。以下代码可解决:// 主进程const el = require(’electron’);el.app.testMain = { testName: ‘main process object’, testCallback: function(callback) { callback.apply(null, [this]); }};// 渲染进程const mt = require(’electron’).remote.app.testMain;mt.testCallback(function(ref) { console.log(ref.testName);});

February 21, 2019 · 1 min · jiezi

Electron打包,NSIS修改默认安装路径

我们用NSIS打包electron做的exe时,默认安装路径都是C盘,如果想要修改默认安装路径,就需要写个NSIS脚本来修改。 1.package.json的nsis里添加: 2.新建文件

February 15, 2019 · 1 min · jiezi

GitNote 基于 Git 的跨平台笔记软件正式发布

GitNote 基于 Git 的跨平台笔记软件为什么自从工作之后,我开始进行笔记记录,这是一个很棒的习惯.我曾经使用过 EDiary Evernote Onenote Wiz 麦库等,都是一些不错的笔记软件,但是都有一些各式各样的问题,不能满足我的使用.2013 年,我用 java 编写了第一款笔记软件 jnote,支持 markdown 和富文本编辑器,但是没有云同步功能.2016 年,我用 electron 和 JavaScript 编写了一个 markdown 编辑器 ndpeditor,不是笔记软件.2017 年,我用 electron 和 JavaScript 编写了基于 git 的 GitNote 笔记软件,这个采用一个 React开发的版本,这是一个没有发布的版本.2018 年,我用 Vue重构了 GitNote,更强大的 GitNote.git 同步git 是一个很棒的工具,GitNote 支持 git 的全部特性,并且不依赖本地 Git 环境. 你可以使用任何支持 Git 的仓库.https://github.com/ 免费版支持无限私有仓库https://BitBucket.com/ 免费版支持私有仓库https://gitlab.com/ 免费版支持私有仓库https://gitee.com/ 免费版支持私有仓库(推荐)https://coding.net/ 免费版支持私有仓库还有很多GitNote 是一款基于 Git 的跨平台笔记软件,内置 git 支持,无须本地有任何 git 环境,拥有 git 的全部特性,可以任意的恢复笔记版本记录,依托 github 的免费不限量私人仓库,你的笔记没有空间的限制,你的数据完全属于你自己.TODO轻量级的 todo 管理,在笔记中快速便捷创建 Todo ,没有复杂管理流程,只需要关注完成和未完成.用极简的方式来管理自己的 todo.富文本编辑器富文本编辑器,不仅支持各种复杂的文字编辑,还支持快捷键、公式、语法高亮、Todo、图片粘贴,等等功能.MARKDOWN不仅是一款漂亮的 markdown 的编辑器,而且还支持编写幻灯片功能,方便你进行幻灯片演讲.附件不仅仅支持各种各样的文件作为附件,还能自动识别图片,将图片插入到笔记中.跨平台Mac windows Linux 全平台支持,未来也会对移动端进行支持.收藏通过浏览器插件,可以收集网络上面的任何内容,自动同步到你的笔记仓库中.扩展提供强大丰富的扩展 API,可以自由的定制功能扩展插件,可以为提供思维脑图,番茄工作法,图床等等功能特色插件思维脑图支持思维脑图,帮助我们用形象化的方式,科学的处理事情,比如记录和激发创意,并且支持多种导出方式.流程图支持制作流程图,流程图,组织结构图,UML,ER 和网络拓扑图等,并且支持多种导出方式.演示文稿使用 markdown 可以很容易的创建出 web 版的演示文档,并支持导出.多图床支持多个图床平台上传,自动插入到笔记中,提供 API 可以自由定制自己的图床.官网地址:https://gitnoteapp.com/下载地址:https://gitnoteapp.com/zh/#do… ...

February 1, 2019 · 1 min · jiezi

使用webpack + electron + reactJs开发windows桌面应用

electron是一两年前挺火的一个框架本质上是一个浏览器,但是集成了很多windows系统的功能,让前端开发也可以直接操作windows的窗体,做成一个实打实的桌面软件(当然听说mac上也可以用electron,不过没试过)(没错我还在用windows,不是mac也不是linux,我是个lowB)团队主要的技术栈是react,所以考虑用react开发,方便维护。PS.由于项目是大半年前做的,所以一些细节可能记忆有误请见谅几个重点:1.想要能调试必须使用webpack打包,不能用react那些常用的打包脚手架,因为webpack打包有target: “electron-main"2.对于不使用electron模块的项目,electron可以直接跑任何网页;对于用到electron模块的项目,如果不设置target: “electron-main”,而直接用webpack打包(或者其他的打包工具),打包工具会直接默认把electron模块一起打包进去。而electron模块里会用到node的比如fs模块,这些模块都不允许在网页上调用,因为需要直接访问电脑文件下面开始我们知道electron其实是有两个部分的,一个是窗体部分,一个是窗体里运行的网页项目窗体部分通常放在根目录下,只使用main.js一个文件来控制网页项目部分一般放在src目录下,打包出来的文件放到dist目录下目录大致如下main.js文件里会对窗体部分做很多配置具体可以参见electron的官方文档:electron官方文档mainWnd = new BrowserWindow({ // 窗体配置参数});mainWnd.loadURL(file://${__dirname}/dist/index.html); //这句话是用于配置窗体加载的网页项目的,配置为打包后的目录网页项目部分使用ipc模块与electron的窗体部分的ipcMain模块进行通信,网页项目部分可以发送以某个指令给窗体部分网页项目部分发送指令// src/MyComponent.jsconst ipc = require(’electron’).ipcRenderer;ipc.send(’logout’);窗体部分接收到指令后做相应的行为//main.jsipcMain.on(’logout’, function (event, arg) { // do something console.log(’logint’);});窗体部分也可以使用webContent模块与网页项目部分通信比如用户点击关闭窗体,可以使用event.preventDefault();阻塞关闭,然后通知网页项目部分,做退出登录的行为,退登完成之后再关闭窗体// main.js mainWnd.webContents.send(‘mainWnd-close’);网页项目部分做对应的行为比如退出登录,退出登录完成后,也使用ipc通知窗体部分,窗体接收到’logout-succ’后,执行关闭窗体的行为。// src/MyComponent.jsipc.on(‘mainWnd-close’, () => { // do something ipc.send(’logout-succ’);})·在开发项目时,可以先用网页的形式开发项目,等到网页项目部分差不多完成后,再注入electron中,开发网页项目部分和窗体部分的交互·在webpack中使用target: “electron-main"后,webpack将不会打包有关eletron的代码

January 31, 2019 · 1 min · jiezi

使用Rust + Electron开发跨平台桌面应用 ( 二 )

前言在上一篇文章使用Rust + Electron开发跨平台桌面应用 ( 一 )中,我们将Rust + Electron结合起来,使用Rust编写核心业务逻辑,并编译成node库提供给Electron的UI界面调用,但是在上一篇文章中发现遇到了很多问题,尤其是Electron 的版本和 Rust编译出来的版本必须要一致,否则会无法调用成功,这就很坑了,所以为了改变这一情况,今天我们将使用另一种方式将Rust的代码提供给Js进行调用,这就是FFI。FFI是什么FFI(Foreign Function Interface)是用来与其它语言交互的接口,由于现实中很多程序是由不同编程语言写的,必然会涉及到跨语言调用,这时一般有两种解决方案:1、将函数做成一个服务,通过进程间通信(IPC)或网络协议通信(RPC, RESTful等);2、直接通过 FFI 调用。前者需要至少两个独立的进程才能实现,而后者直接将其它语言的接口内嵌到本语言中,所以调用效率比前者高。Rust作为系统级编程语言,也是对FFI提供了完善的支持。mangle由于rust支持重载,所以函数名会被编译器进行混淆,就像c++一样。因此当你的函数被编译完毕后,函数名会带上一串表明函数签名的字符串。这样的函数名为ffi调用带来了困难,因此,rust提供了#[no_mangle]属性为函数修饰。 对于带有#[no_mangle]属性的函数,rust编译器不会为它进行函数名混淆, 如:#[no_mangle]pub extern fn test() {}下面我们来编写一个thread_count.rs,其实跟寻常的rust代码没有什么区别:#[no_mangle]pub extern fn threadcount(x: i32) -> i32 { let result: i32 = num_cpus::get() as i32; return result * x;}指定库类型rust默认编译成rust自用的rlib格式库,要让rust编译成动态链接库或者静态链接库,需要显示指定,一共有三种方式,我这里采用的是直接在Cargo.Toml文件中指定,如下:[lib]name = “thread_count"crate-type = [“dylib”]需要注意的是name,必须符合rust的包结构,能够在src目录下找到。我们执行cargo build命令,可以看到,在/target/debug目录下生成了我们需要的文件libthread_count.dylibJS使用rust的动态链接库那么我们要如何在JS中调用rust生成dylib呢?答案就是ffi-napi,我们使用ffi-napi这个包来在js中调用ffi,话不多说,直接看代码let ffi = require(‘ffi-napi’);let path = require(‘path’);let threadCount = ffi.Library(path.join(__dirname, ‘./target/debug/libthread_count’), { threadcount: [‘int’, [‘int’]]});let result = threadCount.threadcount(12);console.log(“thead_count: " + result);结果如下:好了,到此为止,我们就成功的将rust编译成动态链接库给JS调用了,这种方式是我觉得比较好的一种方式,虽然引入函数的方式比较丑,但是我们不用担心node版本的问题。结语虽然FFI是一种我认为比较好的方式,但是它也不是完美无缺的,例如,在跨越FFI的过程中,我们会丢失rust的类型信息,从而引发安全性问题,当然这也不是没有解决办法,我们可以使用rust的Box来包装我们的类型,这个可以单独开一篇文章来讲述,就不展开了(先挖个坑,哪天想起来再填)

January 31, 2019 · 1 min · jiezi

使用Rust + Electron开发跨平台桌面应用 ( 一 )

前言近段时间学习了Rust,一直想着做点什么东西深入学习,因为是刚学习,很多地方都不熟悉,所以也就不能拿它来做编译器这些,至于web开发,实际上我并不建议拿这个来学习一门语言,大概有几个方面,一是web开发的套路无非也就那么几个,对学习一门语言并不会有多大的帮助。二是web开发大多已经被封装了很多东西,对学习语言本身其实不利,真的要深入学习的话还是建议从语言本身出发,尽量不要用封装好的东西,当然,标准库除外。为什么是Rust + Electron原因其实很简单,我不想做太复杂的东西,因为大部分的精力还是要放在工作上,其次是希望做一个我日常能用的东西,当然现在还没想好,可能是个音乐播放器,也可能是个天气展示的app,这样我就可以每天使用了,这也会更有动力促使我开发好它。Rust 和 Electron 想必就不用我多介绍了吧,至于为什么是这个组合可以查看知乎的这个问题,我赞同的是的方案是使用 C/Cpp/Rust 开发的核心 + Electron / Qt 开发界面本期目标本期的目标非常简单,将Rust 和 Electron结合起来,使用Rust获取电脑cpu核数,Electron将数据绘制在界面上展示。初始化Electron项目Electron项目的初始化我用的工具是electron-forge,首先我们按照electron-forge的官网介绍来npm install -g electron-forgeelectron-forge init my-new-projectcd my-new-projectelectron-forge start解释一下,首先我们要安装electron-forge,这是一个脚手架工具,类似于Vue-cli。然后我们初始化一个项目,项目名称为my-new-project。需要注意的是这初始化的过程中electron-forge会构建package.json, 然后下载依赖,我第一次下载依赖的时候卡在了electron-runtime,第二次重试的时候就好了。第二个是electron-forge中的依赖会对Python版本有要求,只能要求Python2,这里要注意的一点是,我十分不建议使用pyenv来控制Python版本,会出现以下错误,我的解决方式是使用virtualenv新建一个Python2 的环境。Fatal Python error: PyThreadState_Get: no current thread现在我们来看一下项目结构整个项目结构非常简单,src中是我们的源文件,index.html是界面文件,index.js是界面逻辑文件,大家打开index.js就可以看到一段自动生成的代码,主要是创建了一个app,以及监听app的活动,需要注意到的是其中对mac的处理。app.on(‘window-all-closed’, () => { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== ‘darwin’) { app.quit(); }});好了,现在让我们把项目跑起来,在项目目录下执行electron-forge start命令,稍等一会我们就可以看到界面运行起来了初始化Rust项目在开发之前我们要知道,JS是无法直接运行Rust的,就像JS无法直接运行C++一样。所以我们需要将Rust打包成Node模块提供给JS进行调用。所以我们会使用neon来做到这件事,neon的github地址在这里首先我们需要安装neon,注意,neon对python版本也是有要求的,如果你是mac,python版本必须要是Python2.7,不支持Python3,同样,这里也会出现上面说过的no current thread问题,所以我们在开发时最好用virtualenv新建一个Python2的环境。安装完neon之后我们执行neon new thread-count,新建一个项目。看一下项目结构lib是我们最终的导出文件,提供给electron进行调用,native下则是我们的rust代码,注意,这里的入口文件是native/src/lib.rs,因为我们建立的是一个库而不是一个可执行的应用程序。让我们先编译项目,在文件目录下执行neon build –release命令。让我们进入终端调用一下项目试试:成功!到现在我们就成功的将rust写的代码封装成node库,使得JS可以进行调用了,接下来我们回到上面说过的,将rust的功能更改为获取CPU核数,然后将它封装成一个函数并进行导出。首先我们要修改Cargo.toml,在[dependencies]下增加一个num_cpus = “1.4.0"的依赖项,然后修改native/src/lib.rs文件如下#[macro_use]extern crate neon;use neon::prelude::*;fn thread_count(mut cx: FunctionContext) -> JsResult<JsNumber> { Ok(cx.number(num_cpus::get() as f64))}register_module!(mut cx, { cx.export_function(“thread_count”, thread_count)});修改lib/index.js如下:var addon = require(’../native’);module.exports = addon.thread_count;然后我们再进行编译,执行neon build –release命令,然后再进入终端调用这个函数试试成功啦,至此,我们就成功的将rust代码封装给JS进行了调用。需要注意的是编译rust的node版本需要与运行electron的node版本一致,否则会出现无法调用的情况。好了,到此第一期就结束了,代码我会抽空整理到github,以供有需要的同学查看。最后看一下效果图吧ps: 现在Rust的各项工具和库都不是很成熟,所以大家再实践过程中会遇到各种问题,都可以评论到下面大家一起讨论。 ...

January 30, 2019 · 1 min · jiezi

基于electron制作一个node压缩图片的桌面应用

压缩图片桌面应用imagemin-electron基于electron制作一个node压缩图片的桌面应用下载地址:https://github.com/zenoslin/imagemin-electron/releases项目源码Github:https://github.com/zenoslin/imagemin-electron准备工作我们来整理一下我们需要做什么:压缩图片模块获取文件路径桌面应用生成压缩图片我们需要使用imagemin这个库来压缩图片,这里我们把这个库封装成压缩模块。const imagemin = require(‘imagemin’)const imageminMozjpeg = require(‘imagemin-mozjpeg’)const imageminPngquant = require(‘imagemin-pngquant’)const imageminGifsicle = require(‘imagemin-gifsicle’)async function compass(input, output, opts, callback) { let log = await imageminCompass(input, output, opts) callback(log)}async function imageminCompass(input, output = ’temp’, opts = {}) { input = (typeof input == ‘string’) ? [input] : input; return await imagemin(input, output, { use: [ imageminMozjpeg(opts), imageminPngquant(opts), imageminGifsicle({ optimizationLevel:3 }) ] }) .then(file => { return { status: true, data: file }; }) .catch(e => { console.log(e); return { status: false, error: e.toString() } });}module.exports = { compass: compass};获取文件路径在我的理解中,electron用的是一个mini版的chrome浏览器,然后帮我们实现了浏览器跟系统(win & mac)交互的的许多api接口。我们可以通过正常写网页的方式进行开发,当需要进行与系统交互的操作时,我们只需要在我们网页中的js进程(这里应该叫做这个桌面应用的渲染进程)抛出一个事件,然后在electron的主进程进行监听,收到事件后调用相应的api接口,结果再反过来用事件的方式抛给渲染进程。electron的安装和学习可以上官网https://electronjs.org/进行学习。ps:这里有一个electron的坑说一下,electron和jquery存在冲突,所以直接用script标签引入会失败,在windows对象中找不到jQuery对象。这里我们可以加这么一句解决。<script src="./src/jquery.min.js"></script><script>if (typeof module === ‘object’) {window.jQuery = window.$ = module.exports;};</script>回到正题,首先我们在index.html中增加一个按钮来打开系统的路径选择器。<button id=“input-btn”>选择路径</button>在渲染进程renderer.js中,监听按钮的点击,以及监听主线程返回的事件。const {ipcRenderer} = require(’electron’)const inputBtn = document.getElementById(‘input-btn’)inputBtn.addEventListener(‘click’, (event) => { console.log(‘点击输入按钮’) ipcRenderer.send(‘open-file-dialog-input’)})ipcRenderer.on(‘input-path’, (event, path) => { console.log(收到完成信息 ${path}) _inputPath = path inputPath.value = ${path}})在主进程main.js中,监听渲染进程抛出的事件,并调用api接口后放回结果。ipcMain.on(‘open-file-dialog-input’, (event) => { dialog.showOpenDialog({ properties: [‘openFile’, ‘openDirectory’] }, (files) => { if (files) { console.log(‘发出完成信息’) event.sender.send(‘input-path’, files) } })})这样我们完成了使用系统api接口选择路径的功能。但其实我们实际的使用场景中路径选择器的方式并不是特别的方便,所以我们实现另一个功能。拖动将文件或者文件夹拖入网页中,获取到对应的路径。这里使用了js+div实现了这个功能。index.html<!–可拖入区域–><div id=“holder” class=“jumbotron holder”></div><style> /* 拖拽的区域样式 / .holder { min-height: 200px; background: #eee; margin: 2em; padding: 1em; border: 0px dotted #eee; border-radius: 10px; transition: .3s all ease-in-out; } / 拖拽时用jQuery为其添加边框样式的class */ .holder-ondrag { border: 20px dotted #d45700; }</style>renderer.jsconst holder = document.getElementById(“holder”)holder.ondragenter = holder.ondragover = (event) => { event.preventDefault() holder.className = “jumbotron holder-ondrag”}holder.ondragleave = (event) => { event.preventDefault() holder.className = “jumbotron holder”}holder.ondrop = (event) => { // 调用 preventDefault() 来避免浏览器对数据的默认处理 //(drop 事件的默认行为是以链接形式打开 event.preventDefault() holder.className = “jumbotron holder” var file = event.dataTransfer.files[0] _inputPath = inputPath.value = file.path}将我们获取到的文件路径传入前面编写的压缩文件模块,这样我们就可以完成了图片的压缩。桌面应用生成最后,我们利用electron-packager完成对electron桌面应用的打包。//macelectron-packager . –out=out –platform=mas –arch=x64//winelectron-packager . –platform=win32 –arch=x64ps:在非Windows主机平台上进行打包,需要安装Wine 1.6或更高版本 ...

January 29, 2019 · 2 min · jiezi

【已解决】使用vue-electron脚手架进行vuex赋值时,失败的解决办法。

1、初步尝试我首先尝试用mutation(commit)传参。结果控制台报错:[Vuex Electron] Please, don’t use direct commit’s, use dispatch instead of this.好好好。那我再用action传参试试。虽然控制台没报错,但却一直无法赋值!2、查找资料我找到一个解决方法:注释掉store目录下index.js的createSharedMutations插件。经测试确实可以!但不知道为什么。3、深入研讨经过进一步的查阅。我了解到,刚才传值失败,是因为electron-vue脚手架引入了vuex-electron介个插件。点击查看vuex-electron的文档文档中明确注明了:In case if you enabled createSharedMutations() plugin you need to create an instance of store in the main process. To do it just add this line into your main process (for example src/main.js):import ‘./path/to/your/store’意思是:如果你启用了这个插件,需要在主进程导出(export )store的实例。于是我在主进程中加上了这一句:import ‘../renderer/store’再次运行,赋值成功!4、反思vuex-electron介个插件,用于多进程间共享Vuex Store的状态。如果没有多进程交互的需求,完全可以不引入这个插件。再进一步思考。之前我都是图方便,直接用脚手架。但它们有可能加载不必要的插件。(甚至会导致兼容问题)需要注意~

January 28, 2019 · 1 min · jiezi

遇见创作 · 遇见 Hve Notes

作为一个技术人,你是否喜欢创作,或是怀念过往又或是畅想未来,或是总结技术又或是记录生活?诚然,现在有很多平台或技术社区,里面有很多人每天都在分享,或是读书笔记又或是项目实践,或是面试经历又或是年终总结。技术社区或平台固然很热闹,但是有一些东西,又想记录在某个地方,只给有缘人。所以这正是个人博客存在的原因之一吧。你可以在自己的博客中记录各种各样的内容,电影影评、旅行日记、编程踩坑经历等等。基于此,你可能使用过或正在使用一些流行的工具如 Hexo、Jekyll 等静态博客网站生成器,又或者是 Gatsby 这种新型的 Web 构建工具。不过,你可能在寻找或者期待这样一种工具,可以更便捷地来管理博客和更舒适的编辑内容,于是今天的主角—— Hve Notes 诞生了!下面奉上介绍给各位喜欢创作的朋友???? 欢迎使用 Hve Notes ! Github: Hve Notes 项目主页: Hve Notes 示例网站: 示例网站一 示例网站二 ✍️ Hve Notes 一个静态博客写作客户端。你可以用它来记录你的生活、心情、知识、笔记、创意… …特性???????? 你可以使用最酷的 Markdown 语法,进行快速创作 ???? 你可以给文章配上精美的封面图和在文章任意位置插入图片 ????️ 你可以对文章进行标签分组 ???? 你可以自定义菜单,甚至可以创建外部链接菜单 ???? 你可以在 ???????????????????????????? 或 ???????????????????? 设备上使用此客户端 ???? 你可以使用 ???????????????????????? ???????????????????? 或 Coding Pages 向世界展示,未来将支持更多平台 ???? 你可以进行简单的配置,接入 Gitalk 或 DisqusJS 评论系统 ???????? 你可以使用中文简体或英语 ???? 你可以任意使用应用内默认主题或任意第三方主题 ???? 当然 Hve Notes 还很年轻,有很多不足,但请相信,它会不停向前????未来,它一定会成为你离不开的伙伴尽情发挥你的才华吧!???? Enjoy~目前,Hve Notes 已经更新到了 v0.7.0 版本,基本已经可以满足日常使用。当然还有很大的成长空间,例如文章和标签的 slug 格式定制和可编辑,个性化的页面增加与配置,赋予用户更强的扩展能力,更丰富的主题等等。在此,也欢迎感兴趣的朋友可以加入我们,提出建议或共同开发,也欢迎加入我们的 Telegram 群组如果您觉得此项目还不错,欢迎 Star 支持作者,也欢迎体验之后提出您最宝贵的建议到 Issue ...

January 21, 2019 · 1 min · jiezi

Electron 页内查找模块介绍

简介实现Electron app页内按关键字查找匹配文本的功能特征依赖于Electron的findInPage API支持使用者灵活配置UI界面支持区分大小写当用户输入时自动查找查找输入框文本隔离,不会被匹配到支持以下Electron版本 ^1.8.7, ^2.0.0, ^3.0.0, ^4.0.0支持以下平台 Windows, Linux, Mac演示默认UI定制化UI安装$ npm install electron-find –save使用# 引入模块import { remote, ipcRenderer } from ’electron’import { FindInPage } from ’electron-find’# 使用默认配置来创建实例let findInPage = new FindInPage(remote.getCurrentWebContents())findInPage.openFindWindow()# 开启预加载选项,创建实例的时候会同时加载查找窗口相关domlet findInPage = new FindInPage(remote.getCurrentWebContents(), { preload: true})findInPage.openFindWindow()# 配置父节点元素, 默认为 document.bodylet findInPage = new FindInPage(remote.getCurrentWebContents(), { parentElement: document.querySelector(’#id’)})findInPage.openFindWindow()# 配置查找窗口显示或隐藏的过渡周期, 默认为 300 (ms)let findInPage = new FindInPage(remote.getCurrentWebContents(), { duration: 200})findInPage.openFindWindow()# 配置查找窗口相对于父级定位节点的偏移量let findInPage = new FindInPage(remote.getCurrentWebContents(), { offsetTop: 20, offsetRight: 30})findInPage.openFindWindow()# 自定义UI界面颜色let findInPage = new FindInPage(remote.getCurrentWebContents(), { boxBgColor: ‘#333’, boxShadowColor: ‘#000’, inputColor: ‘#aaa’, inputBgColor: ‘#222’, inputFocusColor: ‘#555’, textColor: ‘#aaa’, textHoverBgColor: ‘#555’, caseSelectedColor: ‘#555’})findInPage.openFindWindow()# 参考demonpm installnpm run e快捷键Enter: 查找下一个 Shift + Enter: 查找上一个 Esc: 关闭窗口另外, 可以参考demo,使用全局快捷键来打开窗口。API类: FindInPage new FindInPage(webContents, [options]) webContents Object(required) - 渲染进程的webContents对象 options Object(optional) preload Boolean - 创建实例的时候是否预加载查找窗口。 默认为 false。 parentElement Object - 指定查找窗口的父级节点。 默认为 document.body。 duration Number - 指定查找窗口显示或隐藏的过渡周期。 默认为 300 (ms)。 offsetTop Number - 指定查找窗口相对于父级定位元素顶部偏移量。 默认为 5。 offsetRight Number - 指定查找窗口相对于父级定位元素右边偏移量。 默认为 5。 boxBgColor String - 配置查找窗口背景色。 默认为 “#ffffff”。 boxShadowColor String - 配置查找窗口阴影色。 默认为 “#909399”。 inputColor String - 配置输入框文本颜色。 默认为 “#606266”。 inputBgColor String - 配置输入框背景颜色。 默认为 “#f0f0f0”。 inputFocusColor String - 配置输入框聚焦时的边框颜色。 默认为 “#c5ade0”。 textColor String - 配置查找窗口中文本颜色。 默认为 “#606266”。 textHoverBgColor String - 配置鼠标悬停文本时的背景色。 默认为 “#eaeaea”。 caseSelectedColor String - 配置区分大小写选项选中时的边框颜色。 默认为 “#c5ade0”。实例方法使用new FindInPage 创建的实例具有以下方法: findInPage.openFindWindow() 当查找窗口关闭时,打开窗口。 当查找窗口已经打开时,聚焦输入框。 findInPage.closeFindWindow() 关闭窗口。 findInPage.destroy() 关闭窗口,清除对象的引用,释放内存。 查看更多:https://github.com/TheoXiong/electron-find ...

January 15, 2019 · 1 min · jiezi

推荐一款可以多平台上使用的控制台工具

先强调一下,这个是可以在windows, linux, mac 多平台上使用的以前找过ubuntu上介绍的控制台工具,反正一个字都是丑。这里通过electron官网搜索了使用electron开发的软件,发现了一款不错的控制台工具(上面还有其它的比较好用的),推荐给大家Extraterm这里是github的官方地址Github喜欢这个的给个赞吧

December 29, 2018 · 1 min · jiezi

Windows 下的 electron 开发笔记一

前言根据公司业务需求,使用 electron 开发桌面 BrowserWindow 应用。参考 API:Electron 文档安装与配置安装工具node(LTS版)git 命令行工具搭建项目初始化:$ npm init安装 electron:$ npm install electron –save-dev软件打包安装打包工具:$ npm install –save-dev electron-packager打包基本命令:electron-packager {location} {name} {platform} {architecture} {version} {options}location:项目所在路径name of project:打包的项目名字platform:确定了你要构建哪个平台的应用(Windows、Mac 还是 Linux)architecture:决定了使用 x86 还是 x64 还是两个架构都用version:electron 的版本options:可选选项在 package.json 中添加配置项:“packager”: “electron-packager ./ writ win x86 –app–version=2.0.6 –overwrite –icon=./favicon.ico"执行:$ npm run-script packager环境依赖.netframework 4.5.1python2.7Visual C++ Build Tools一键安装:$ npm install –global –production windows-build-tools环境设置:$ npm config set msvs_version 2015若出现 vc2015 安装失败情况,请自行安装 SP1windows6.1-KB976932 补丁插件依赖node-gyp Node 编写的跨平台命令行工具,用于编译 Node.js 的原生插件模块$ npm install -g node-gypffi 用以调用动态库的 Node.js 插件$ npm install ffi –savebuffer 提供与 Node.js 的 Buffer 完全相同的缓冲区插件$ npm install buffer –saveiconv-lite 用于在 Node.js 当中处理在各种操作系统出现的各种奇特编码,该模块不提供读写文件的操作,只提供文件编码转换的功能$ npm install iconv-lite –saveelectron-rebuild 用以重编译适合 electron 的模块$ npm install electron-rebuild –save-dev$ ./node_modules/.bin/electron-rebuild ./node_modules/ffi 占坑 ...

December 26, 2018 · 1 min · jiezi

electron-vue使用electron-updater实现自动更新

今天呢,给大家带来一篇干货满满的electron-vue自动升级的教程,话不多说,开始我的表演!配置文件 “package.json"“build” : { “publish”: [ { “provider”: “generic”, “url”: “http://127.0.0.1:3000/newfile/” } ],},“devDependencies”: { “electron”: “^2.0.4”, “electron-updater”: “3.0.0”, “electron-builder”: “^20.19.2”}build字段详细配置自动更新 “update.js"import { autoUpdater } from ’electron-updater’import { ipcMain } from ’electron’/** * -1 检查更新失败 0 正在检查更新 1 检测到新版本,准备下载 2 未检测到新版本 3 下载中 4 下载暂停 5 下载暂停恢复 6 下载完成 7 下载失败 8 取消下载 * /class Update { mainWindow constructor (mainWindow) { this.mainWindow = mainWindow autoUpdater.setFeedURL(‘http://127.0.0.1:3000/newfile’) // 更新地址与package.json中的build.publish.url相对应 /* * 根据自身需求选择下方方法 */ this.error() this.start() this.allow() this.unallowed() this.listen() this.download() } Message (type, data) { this.mainWindow.webContents.send(‘message’, type, data) } error () { // 当更新发生错误的时候触发。 autoUpdater.on(’error’, (err) => { this.Message(-1, err) console.log(err) }) } start () { // 当开始检查更新的时候触发 autoUpdater.on(‘checking-for-update’, (event, arg) => { this.Message(0) }) } allow () { // 发现可更新数据时 autoUpdater.on(‘update-available’, (event, arg) => { this.Message(1) }) } unallowed () { // 没有可更新数据时 autoUpdater.on(‘update-not-available’, (event, arg) => { this.Message(2) }) } listen () { // 下载监听 autoUpdater.on(‘download-progress’, () => { this.Message(‘下载进行中’) }) } download () { // 下载完成 autoUpdater.on(‘update-downloaded’, () => { this.Message(6) setTimeout(m => { autoUpdater.quitAndInstall() }, 1000) }) } load () { // 触发器 autoUpdater.checkForUpdates() }}export default Updateupdate配置若对class写法不是很熟悉的同学,可以参考阮一峰老师的es6课程class在下载中的时候有一个问题就是下载非差异文件的时候不会触发下载监听,点击查看主进程 “index.js"import Update from ‘./update’ mainWindow = new BrowserWindow({ height: 563, useContentSize: true, width: 1000})let update = new Update(mainWindow) ...

December 24, 2018 · 1 min · jiezi

electron 网页和主进程通讯

前端网页import { RxIpc} from ‘rx-ipc-electron/lib/rx-ipc’;export const IsElectron = window.navigator.userAgent.toLowerCase().indexOf(’electron’) !== -1;export function ajax(config) { if (!IsElectron) return Promise.reject(‘only for electron’); const { ipcRenderer } = eval(require('electron')); const rxIpc = new RxIpc(ipcRenderer); return new Promise((resolve, reject) => { rxIpc.runCommand(‘ajax’, null, config).subscribe(resolve, reject); });}window[’test1’] = async function test1() { const url = ‘https://www.baidu.com/s'; const params = { wd: 1 }; const rsp = await ajax({ url, method: ‘get’, params, }); console.log(rsp);}electron进程import rxIpc from ‘rx-ipc-electron/lib/main’;import { Observable } from ‘rxjs’;import axios from ‘axios’;rxIpc.registerListener(‘ajax’, config => { return Observable.from(axios(config));}); ...

December 19, 2018 · 1 min · jiezi

electron 实现 微信开发者工具 devTools

devTool开发者工具解决的问题部署到第三方APP的代码调试!第三方服务接入到APP的调试!客户端 [类似微信开发者工具公众号模式] :采用 electronjs 构建跨平台应用。集成浏览器内核 & 客户端插件,构建基本的模拟运行环境!服务:采用扫码验证之后,自动构建一个本地或者公网 服务 。客户端临时访问该服务!调试 [类似微信开发者工具小程序模式] :采用google开源的 devtools 代理客户端和服务端,连接真机和客户端间的断点和其他常见调试!usesnpm install –save-devnpm run start效果 devTools源码链接:https://github.com/UIorPM/ele…

December 14, 2018 · 1 min · jiezi

Electron + Antd + Mobx 环境搭建

最近要重构一版桌面管理软件。业务需求与WEB版基本一致,但需要用到断点续传等本地功能。最经济的办法当然是移植到Electron环境中。之前的应用框架主要用到了以下React套餐:ReactReact-Router 路由Mobx 数据管理AntDesign 界面组件经过一天时间的摸索大致找到了门路,构建完成后写了一个脚手架,欢迎下载。下面回顾一下本次环境搭建的过程。安装AntDesign的脚手架关于Antd在CRA框架下的使用,官网有一份很详细的操作说明(包括babel-import等的设置),初学者可以跟着一步一步学习。如果你赶时间,也可以直接用它的脚手架。$ git clone https://github.com/ant-design/create-react-app-antd.git my-project$ cd my-project$ npm install && npm start跑起来以后,会自动弹出一个页面 localhost:3000,网页上显示的就是最基础的包括了antd的demo示例。安装Electron及配置关于electron的介绍,可以看官网文档,本文不涉及。完成antd脚手架安装后,在工程目录下安装electron。$ npm install electron –save-dev安装完成以后,在根目录下需要做一些设置,包括:electron启动脚本npm启动及编译命令render端使用electron模块的设置electron启动脚本在根目录下新建main.js,写入以下内容。(其实与官方例子是一样的)// Modules to control application life and create native browser windowconst {app, BrowserWindow} = require(’electron’)// Keep a global reference of the window object, if you don’t, the window will// be closed automatically when the JavaScript object is garbage collected.let mainWindowfunction createWindow () { // Create the browser window. mainWindow = new BrowserWindow({width: 800, height: 600}) // and load the index.html of the app. mainWindow.loadFile(‘build/index.html’) // Open the DevTools. // mainWindow.webContents.openDevTools() // Emitted when the window is closed. mainWindow.on(‘closed’, function () { // Dereference the window object, usually you would store windows // in an array if your app supports multi windows, this is the time // when you should delete the corresponding element. mainWindow = null })}// This method will be called when Electron has finished// initialization and is ready to create browser windows.// Some APIs can only be used after this event occurs.app.on(‘ready’, createWindow)// Quit when all windows are closed.app.on(‘window-all-closed’, function () { // On macOS it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== ‘darwin’) { app.quit() }})app.on(‘activate’, function () { // On macOS it’s common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (mainWindow === null) { createWindow() }})npm启动及编译命令在工程根目录的package.json中加入以下内容:{ … “main”: “main.js”, “homepage”: “.”, “scripts”: { “electron-dev”: “electron . –env dev”, “electron-build”: “npm run build && electron . –env build”, “start”: “react-app-rewired start”, … }}我们希望在执行npm run electron-dev时,能打开electron应用并展示antd的网页内容,那么需要修改main.js: … // and load the index.html of the app. // mainWindow.loadFile(‘index.html’) // 这个是原内容,修改为以下内容 if (argv && argv[1] === ‘dev’) { // 如果npm run electron-dev,加载本地网页 mainWindow.loadURL(‘http://localhost:3000/’) } else if (argv && argv[1] === ‘build’) { mainWindow.loadURL(url.format({ pathname: path.join(__dirname, ‘./build/index.html’), protocol: ‘file:’, slashes: true })) }…配置到这里后,打开两个终端,分别cd到项目根目录下。$ npm start // 终端1$ npm run electron-dev // 终端2electron界面应该就会展示出来,界面中显示的就是antd的demo内容。render端使用electron模块经过以上设置,界面内容已经可以正常展示并可以实时刷新,但还缺少了一个重要功能:electron模块,用以支持本地化操作。配置的基本思路是,在electron启动过程中在window对象中设置webPreferences的preload属性[文档],将electron模块注册为global对象供renderer侧使用。… // Create the browser window. // mainWindow = new BrowserWindow({ width: 800, height: 600 }) mainWindow = new BrowserWindow({ width: 800, height: 600, autoHideMenuBar: true, fullscreenable: false, webPreferences: { javascript: true, plugins: true, nodeIntegration: false, // 不集成 Nodejs webSecurity: false, preload: path.join(__dirname, ‘./public/renderer.js’) // public目录下的renderer.js } })…设置成功后,在renderer侧即可引入electron的remote模块。// 新建renderer.js,public目录下const process = require(‘process’);process.once(’loaded’, () => { global.electron = require(’electron’)});使用mobx安装mobx及mobx-react$ npm install mobx mobx-react –save安装成功后,在App.js中使用:…import { observer } from ‘mobx-react’;…@observerclass App extends React.Component { …}执行开发命令后,会发现出现了一个编译错误。原因就在于CRA团队并不倾向于支持所有未正式发布的javascript语言特性,目前要使用装饰圈,均需要使用babel等进行转译。这个问题目前有两个方法:使用eject方式或react-app-rewire-mobx模块。使用eject方式将会暴露出所有的webpack配置,开发者如果熟悉可以进行自定义的配置。当然,这样做的话create-react-app的意义就大打折扣了。使用react-app-rewire-mobx则相对方便了许多,只需安装该模块,并在config-overrides.js中加入,即可正常使用。…const rewireMobX = require(‘react-app-rewire-mobx’);module.exports = function override(config, env) { … config = rewireMobX(config, env); return config;}注意按照上面的方式配置以后,仍然会报错。./src/index.js Error: The ‘decorators’ plugin requires a’decoratorsBeforeExport’ option, whose value must be a boolean. If youare migrating from Babylon/Babel 6 or want to use the old decoratorsproposal, you should use the ‘decorators-legacy’ plugin instead of’decorators’.原因在于,新版的babel7.0的描述换了一种方式。应该将上面的配置改为:module.exports = function override(config, env) { … config = injectBabelPlugin(["@babel/plugin-proposal-decorators", { legacy: true }], config); return config;}其实在react-app-rewire-mobx中也仅仅就是就是导入这个修饰符,只不过是旧版的写法。但就因为没有跟随babel7.0更新,所以导致了这个// react-app-rewire-mobx/index.jsconst {injectBabelPlugin} = require(‘react-app-rewired’);function rewireMobX(config, env) { return injectBabelPlugin(’transform-decorators-legacy’, config);}module.exports = rewireMobX; ...

December 13, 2018 · 3 min · jiezi

electron制作聊天界面(仿制qq)

效果图:样式使用scss和flex布局这也是制作IM系统的最后一个界面了!在制作之前参考了qq和千牛需要注意的点qq将滚动条美化了 而且在无操作的情况下是不会显示的滚动条美化::-webkit-scrollbar { /滚动条整体样式/ width: 5px; /高宽分别对应横竖滚动条的尺寸/ height: 1px;}::-webkit-scrollbar-thumb { /滚动条里面小方块/ border-radius: 10px; -webkit-box-shadow: inset 0 0 5px rgba(228, 57, 60, 0.2); background: rgba(20, 20, 50, 0.6); position: absolute;}::-webkit-scrollbar-track { /滚动条里面轨道/ -webkit-box-shadow: inset 0 0 5px rgba(228, 57, 60, 0.2); border-radius: 10px; background: #EDEDED; position: absolute;}滚动条根据时机显示其实这个也很简单 用的mouseenter 和 mouseleave事件<div :style="{overflowY:messageScroll? ‘auto’ : ‘hidden’,paddingRight: messageScroll ? ‘0’: ‘5px’ }" @mouseenter=“showMessageScrolls” @mouseleave=“hideMessageScrolls”></div># script showMessageScrolls(){ this.messageScroll = true;},hideMessageScrolls(){ this.messageScroll = false;},这里解释一下为什么有一个paddingRight 因为我们的滚动条是5px 如果不加 在滚动条显示的时候页面会抖动 简单写法 @mouseenter=“messageScroll = true” @mouseleave=“messageScroll = false"页面滚动页面打开时消息列表滚动到底部this.$nextTick(function () { this.$refs.msgBox.scrollTop = this.$refs.msgBox.scrollHeight})消息发送滚动到底部 this.$refs.msgBox.scrollTop = this.$refs.msgBox.scrollHeight;内容编辑没有使用表单元素 直接使用的 contenteditable因为contenteditable 没法用双向数据绑定 不过 可以用数据侦听器 有很多办法 但是有很简单的 使用input事件就行了代码页面代码<template> <div class=“friend_window”> <header> <div class=“nickname”>Lee</div> <div class=“buttons”> <i class=“iconfont”>&#xe669;</i> <i class=“iconfont”>&#xe601;</i> </div> </header> <aside> <nav> <ul> <li > <div class=“avatar”><img src=”@/assets/img/1.jpg" alt=""></div> <div class=“msg_box”> <div class=“nickname”>李昊天-</div> <div class=“messages”>最近还好吗</div> </div> <div class=“push_right”> <div class=“time”>12:50</div> <div class=“number”>1</div> </div> </li> <li > <div class=“avatar”><img src="@/assets/img/2.jpg" alt=""></div> <div class=“msg_box”> <div class=“nickname”>李昊天-</div> <div class=“messages”>最近还好吗</div> </div> <div class=“push_right”> <div class=“time”>12:50</div> <div class=“number”>1</div> </div> </li> <li > <div class=“avatar”><img src="@/assets/img/3.jpg" alt=""></div> <div class=“msg_box”> <div class=“nickname”>李昊天-</div> <div class=“messages”>最近还好吗</div> </div> <div class=“push_right”> <div class=“time”>12:50</div> <div class=“number”>1</div> </div> </li> <li > <div class=“avatar”><img src="@/assets/img/4.jpg" alt=""></div> <div class=“msg_box”> <div class=“nickname”>李昊天-</div> <div class=“messages”>最近还好吗</div> </div> <div class=“push_right”> <div class=“time”>12:50</div> <div class=“number”>1</div> </div> </li> <li class=“active”> <div class=“avatar”><img src="@/assets/img/5.jpg" alt=""></div> <div class=“msg_box”> <div class=“nickname”>李昊天1-</div> <div class=“messages”>最近还好吗</div> </div> <div class=“push_right”> <div class=“time”>12:50</div> <div class=“number”>1</div> </div> </li> <li > <div class=“avatar”><img src="@/assets/img/6.jpg" alt=""></div> <div class=“msg_box”> <div class=“nickname”>李昊天-</div> <div class=“messages”>最近还好吗</div> </div> <div class=“push_right”> <div class=“time”>12:50</div> <div class=“number”>1</div> </div> </li> <li > <div class=“avatar”><img src="@/assets/img/7.jpg" alt=""></div> <div class=“msg_box”> <div class=“nickname”>李昊天</div> <div class=“messages”>最近还好吗</div> </div> <div class=“push_right”> <div class=“time”>12:50</div> <div class=“number”>1</div> </div> </li> <li > <div class=“avatar”><img src="@/assets/img/8.jpg" alt=""></div> <div class=“msg_box”> <div class=“nickname”>李昊天-</div> <div class=“messages”>最近还好吗</div> </div> <div class=“push_right”> <div class=“time”>12:50</div> <div class=“number”>1</div> </div> </li> </ul> </nav> <main> <div class=“message_main” ref=“ele” :style="{overflowY:messageScroll? ‘auto’ : ‘hidden’,paddingRight: messageScroll ? ‘0’: ‘5px’ }" @mouseenter=“showMessageScrolls” @mouseleave=“hideMessageScrolls” > <div class=“mes_box” v-for="(item,index) in list" :class="{‘me’ : index % 2 === 0}"> <div class=“avatar”> <img src="@/assets/img/5.jpg" alt=""> </div> <div class=“message_box”> {{item.msg}} </div> </div> </div> <div class=“input_box”> <div class=“menubar”> <svg class=“icon” aria-hidden=“true”> <use xlink:href="#icon-biaoqing-weixiao"></use> </svg> <svg class=“icon” aria-hidden=“true”> <use xlink:href="#icon-folder"></use> </svg> <svg class=“icon” aria-hidden=“true”> <use xlink:href="#icon-tupian1"></use> </svg> <svg class=“icon” aria-hidden=“true”> <use xlink:href="#icon-shuangsechangyongtubiao-"></use> </svg> </div> <div class=“input” ref=“input” contenteditable=“true” @keydown.enter=“sendMsg” @change=“inputMsg” @input=“inputMsg”></div> <div class=“footerbar”> <Button>关闭</Button> <Button type=“primary”>发送</Button> </div> </div> </main> </aside> </div></template>script代码<script> import ‘@/assets/css/scrool.css’ import ‘@/assets/fonts/iconfont.js’; export default { name: “friend”, data() { return { list: [ {msg: ‘赵客缦胡缨,吴钩霜雪明’}, {msg: ‘银鞍照白马,飒沓如流星’}, {msg: ‘十步杀一人,千里不留行’}, {msg: ‘事了拂衣去,深藏身与名’}, {msg: ‘闲过信陵饮,脱剑膝前横。’}, {msg: ‘将炙啖朱亥,持觞劝侯嬴。’}, {msg: ‘三杯吐然诺,五岳倒为轻’}, {msg: ‘眼花耳热后,意气素霓生。’}, {msg: ‘救赵挥金槌,邯郸先震惊。’}, {msg: ‘千秋二壮士,烜赫大梁城。’}, {msg: ‘纵死侠骨香,不惭世上英。’}, {msg: ‘谁能书阁下,白首太玄经。’}, {msg: ‘是唐代大诗人李白借乐府古题创作的一首诗。此诗开头四句从侠客的装束、兵刃、坐骑刻画侠客的形象;第二个四句描写侠客高超的武术和淡泊名利的行藏;第三个四句引入信’}, ], msg: ‘’, number:8, messageScroll:false } }, mounted() { this.$nextTick(function () { this.$refs.ele.scrollTop = this.$refs.ele.scrollHeight }) }, methods: { showMessageScrolls(){ this.messageScroll = true; }, hideMessageScrolls(){ this.messageScroll = false; }, inputMsg(e) { this.msg = e.target.innerHTML; }, sendMsg(e) { this.list.push({msg: this.msg}); this.msg = ‘’; this.$refs.input.innerHTML = ‘’; setTimeout(() => { this.$refs.ele.scrollTop = this.$refs.ele.scrollHeight; }, 200); e.preventDefault(); } } }</script>样式代码.friend_window { position: absolute; width: 100%; height: 100%; background-image: url("../img/main_1.jpg"); border-radius: 4px; -webkit-user-select: none; background-size: 100% 100%; header { height: 40px; background-color: rgba(0, 0, 0, 0.3); -webkit-app-region: drag; border-radius: 4px 4px 0 0; display: flex; justify-content: space-between; .nickname { color: #FFF; line-height: 40px; font-size: 20px; margin: auto; padding-left: 40px } .buttons { i { display: inline-block; color: #FFF; width: 40px; height: 40px; line-height: 40px; text-align: center; cursor: pointer; -webkit-app-region: no-drag; &:hover { background-color: rgba(255, 255, 255, 0.3); } } } } aside { height: calc(100% - 40px); border-radius: 0 0 4px 4px; display: flex; } nav { width: 240px; position: relative; background-size: 100% 100%; overflow-y: auto; &:after { display: inline-block; content: ‘’; width: 5px; cursor: e-resize; position: absolute; right: -2px; top: 0; height: 100%; } ul { li.active { background-color: rgba(255, 255, 255, 0.2); } li { list-style: none; height: 60px; padding-left: 10px; cursor: pointer; display: flex; overflow: hidden; align-items: flex-start; &:hover { background-color: rgba(255, 255, 255, 0.2); } .push_right { padding-right: 10px; text-align: center; align-self: center; .time { font-size: 13px; color: #CFD3DA; } .number { display: inline-block; background-color: #e4393c; color: #fff; min-width: 15px; min-height: 15px; padding: 0 2px; line-height: 15px; border-radius: 50%; text-align: center; font-size: 12px; } } .msg_box { align-self: center; flex: 1; color: #EFF1F3; .messages { color: #CFD3DA; } } .avatar { width: 45px; height: 45px; align-self: center; margin-right: 10px; img { width: 100%; height: 100%; border-radius: 50%; } } } } } main { background-color: #fff; width: calc(100% - 240px); border-radius: 0 0 4px 0; .message_main { height: calc(100% - 35%); overflow-y: auto; &::-webkit-scrollbar { display: block !important; } .mes_box { display: flex; margin-bottom: 10px; margin-top: 10px; padding: 10px; .avatar { width: 40px; height: 40px; margin-right: 10px; img { width: 100%; height: 100%; border-radius: 50%; } } .message_box { background-color: #FFFFFF; color: #333; padding: 10px; border-radius: 5px; max-width: 72%; position: relative; border: 1px solid #D4D4D4; &::before { content: ‘’; display: block; position: absolute; width: 10px; height: 10px; border: 1px solid #D4D4D4; border-right: none; border-top: none; background-color: #FFFFFF; border-radius: 3px; transform: rotate(44deg); left: -6px; top: 14px; } } } .me { display: flex; justify-content: flex-end; .message_box { background-color: #A0E759; color: #333; border: 1px solid #77BF41; &::before { display: none; } &::after { content: ‘’; display: block; position: absolute; width: 10px; height: 10px; border: 1px solid #77BF41; border-bottom: none; border-left: none; border-radius: 3px; background-color: #A0E759; transform: rotate(45deg); right: -6px; top: 14px; } } .avatar { order: 2; margin-left: 10px; } } } .input_box { border-top: 1px solid #ccc; height: calc(100% - 65%); .menubar { height: 30px; width: 100%; display: flex; align-items: center; .icon { display: inline-block; padding: 2px; width: 25px; height: 25px; cursor: pointer; margin-right: 5px; margin-left: 5px; &:hover { background-color: rgba(0, 0, 0, 0.1); } } } .footerbar { display: flex; height: 70px; align-items: center; justify-content: flex-end; padding-right: 20px; button { margin: 0 10px; padding-left: 30px; padding-right: 30px; } } .input { font-size: 16px; padding: 4px 8px; overflow-y: auto; height: calc(100% - 70px - 30px); background-color: #fff; &::-webkit-scrollbar { display: block !important; } } } }}.icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden;}声明代码只为学习使用,如果有个人或者机构使用该代码带来的侵权行为,与本人无关如果代码有不合理之处请大家提出遗留问题有一个问题就是左侧的列表是没法拉伸的 不过已经做了样式了 如果不想要的可以去掉这个css代码 &:after { display: inline-block; content: ‘’; width: 5px; cursor: e-resize; position: absolute; right: -2px; top: 0; height: 100%; } ...

November 25, 2018 · 5 min · jiezi

Hola~ 一款基于Electron的聊天软件

Hola前言本项目旨在从零到壹,制作一款界面精美的聊天软件。Github 地址因为已工作,所以可能没有多少时间来继续跟进这个项目了,项目可优化的点已在下文列出,欢迎大家 Fork 或 Star。ps: 征 logo 一枚。因为本人是开发,设计功底欠缺,所以软件 logo 设计的有点丑,如果有大神有更好的 logo,欢迎 email。技术栈开发环境操作系统:macOS High Sierra v10.13.1编辑器:Visual Studio Code v1.19.1npm:v5.3.0Node:v8.4.0客户端UI设计:Sketch软件框架:Electron界面实现:Vue.js + Vuex + Vue-Router + Webpack通信模块:socket.io-client视频聊天:原生 WebRTC服务端服务器:Node.js后端框架:Koa2通信模块:socket.io数据库:Redis 和 MongoDB软件效果图实现功能[x] 登录注册模块(<手机号+验证码>形式的登录注册)[x] 聊天区模块[x] 最近联系人列表[x] 历史消息(暂未做上拉加载)[x] 私聊[x] 文本消息[x] 图片消息[x] 视频聊天[x] 群聊[x] 文本消息[x] 图片消息[x] 联系人模块[x] 联系人列表[x] 好友资料展示[x] 群组资料展示[x] 删好友,退出或解散群组[x] 功能区模块[x] 添加好友/群组[x] 创建群组[x] 设置区模块[x] 个人资料设置[x] 软件设置[x] 国际化[x] 中文[x] 英文项目目录.├── LICENSE ├── README.md├── client # 客户端代码├── docs # 各种文档(需求文档、UI文档、流程图、数据库设计等)├── preview.png # 软件预览图└── server # 服务端代码反思 & 展望该项目为我大学毕业设计的项目,因时间紧迫,只实现了基本的聊天、加删好友等功能,很多功能还未实现,所以软件还是有很多的瑕疵。为此,我特意思考了很长时间,将待改进的细节或新的功能总结如下:[ ] 历史消息做成上拉瀑布流加载的效果[ ] 为消息注明消息时间、发送状态、已读未读等状态[ ] 为最近联系人列表添加最后一条消息的展示[ ] 为最近联系人添加未读消息个数的统计[ ] 添加好友或加入群组时要进行确认[ ] 为软件的新消息使用系统原生通知窗口通知[ ] 为软件增加原生菜单[ ] 升级输入框,从而可以向输入框直接插入剪切板中的图片[ ] 自己搭建文件服务器,图片服务器(或者使用第三方比如七牛云、阿里云的相关服务)[ ] 为 WebRTC 实现后备方案,搭建 Relay Server,以增强视频聊天的稳定性[ ] 增加网络断开处理的相关逻辑[ ] 了解数据加密相关知识,为消息作加密处理[ ] 为软件做跨平台处理,兼容性方面有待加强[ ] 实现软件自动更新[ ] 接入智能机器人聊天[ ] 实现本地存储历史消息(nedb)[ ] 为软件加入聊天情况分析(比如每天发了多少条消息,与谁聊天最频繁等)扩展阅读初探 Electron - 理论篇初探 Electron - 升华篇XCel 项目总结 - Electron 与 Vue 的性能优化【译】Electron 自动更新的完整教程(Windows 和 OSX)Getting Started with WebRTC通俗易懂:一篇掌握即时通讯的消息传输安全原理即时通讯安全篇(三):常用加解密算法与通讯安全讲解socket.io断线后重连和消息离线存储如何实现Socket.IO stream运用google-protobuf的IM消息应用开发(前端篇)Can one hack “paste image” support into a textarea in Firefox?在线和离线事件 ...

November 5, 2018 · 1 min · jiezi

electron仿制qq(2) 主界面制作

制作从头开始 最后会将写好的组件放到一起的!之前写了好几天的纯css 有点累 本章中将使用sass 如果代码太长 会分两个或多个章节写代码中会有详细的注释 以便于大家阅读and理解界面可能会有部分偏差 比较是仿制的如果你提前看到这句话 表示本文正在更新中 还没更新完!官方界面尺寸默认宽度: 280px (大约 我之前拉伸过 被记录了 所以没法准确的测量)默认高度: 652px (也是大约值)最小高度: 528px最小宽度: 280px最大高度: 1041px (可能不太准确 有可能是根据分辨率来显示的)最大宽度: 605px顶部头像区域高度: 140px底部选项区域高度: 40px搜索框高度: 30px头像直径/高度: 50px右键菜单宽度: 180px下载安装安装electron-vue这几天不知道什么情况 老是下载很慢 如果太慢就挂代理吧!#cd F:\electronvue init simulatedgreg/electron-vue qq_maincd qq_main npm installnpm run dev开始制作创建路由和界面路由:export default new Router({ routes: [ {path: ‘/’, name: ‘main’, component: () => import(’@/components/LandingPage’)}, {path: ‘/main’, name: ‘main’, component: () => import(’@/view/main/index’)}, {path: ‘*’, redirect: ‘/’} ]})创建的第一个窗口 主窗口 不能使用窗口透明 这也就意味着我们不能使用圆角 所以我们要自己再创建一个窗口 让窗口边透明!将主窗口 show:false 暂时不让显示之后再创建一个main.js 让他来创建我们要做的窗口!import {BrowserWindow} from ’electron’let main = null;function createMainWindow() { main = new BrowserWindow({ width: 280, //窗口创建的默认宽度 height: 652, //默认高度 minWidth: 280, //最小宽度 minHeight: 528, //最小高度 maxHeight: 1041, //最大高度 maxWidth: 605, //最大宽度 alwaysOnTop: true, //窗口置顶 useContentSize: true, //使用web网页size, 这意味着实际窗口的size应该包括窗口框架的size,稍微会大一点,默认 frame: false, //去掉顶部 transparent: true, //透明窗口 type: ’toolbar’, //工具栏窗口 webPreferences: { devTools: false, //关闭调试工具 } });}createMainWindow();页面文件和样式文件<template> <div id=“main”> <header> <div class=“toolbar-header”></div> <div class=“search-box”></div> </header> <footer></footer> </div></template><script> export default { name: “index” }</script><style lang=“sass”> #main position: absolute width: 100% height: 100% background-color: red border-radius: 4px header position: relative border-radius: 4px 4px 0 0 height: 140px background-color: blue width: 100% .toolbar-header position: absolute top: 0 height: 33px width: 100% background-color: yellow .search-box position: absolute bottom: 0 width: 100% height: 32px background-color: black footer border-radius: 0 0 4px 4px height: 40px background-color: black position: absolute bottom: 0 width: 100%</style>效果顶部由于图标有点难找 所以找了几个类似的顶部按钮组界面代码:<header> <div class=“toolbar-header”> <i class=“logo iconfont icon-qq”></i> <div class=“buttons”> <span class=“iconfont icon-xunzhang”></span> <span class=“iconfont icon-yifu”></span> <span class=“iconfont icon-Group-"></span> <span class=“iconfont icon-qqkongjian”></span> <span class=“iconfont icon-winfo-icon-zuixiaohua”></span> <span class=“iconfont icon-close close”></span> </div> </div> <div class=“search-box”></div> </header>css代码 header position: relative -webkit-app-region: drag height: 140px background: url(”../../assets/img/bg.png") no-repeat background-size: 100% 100% width: 100% border-radius: 4px 4px 0 0 .toolbar-header position: absolute border-radius: 4px 4px 0 0 top: 0 height: 33px width: 100% line-height: 33px background-color: rgba(255, 255, 255, 0) display: flex .logo color: #808080 margin-left: 10px width: 30px .buttons margin-left: auto color: #FFFFFF -webkit-app-region: no-drag span display: inline-block height: 30px text-align: center width: 30px border-radius: 3px &:hover background-color: rgba(255, 255, 255, 0.3) .close:hover background-color: red border-radius: 0 4px 0 0搜索框界面代码<div class=“search-box”> <div class=“search”> <i class=“iconfont icon-sousuo”></i> <input type=“text” class=“search-input” placeholder=“搜索”> </div></div>css代码 .search-box position: absolute bottom: 0 width: 100% height: 32px background-color: rgba(255, 255, 255, 0.2) -webkit-app-region: no-drag cursor: text color: #FFFFFF line-height: 32px .search i position: absolute left: 10px top: 3px .search-input width: 100% background-color: rgba(255, 255, 255, 0) height: 32px outline: none text-indent: 2rem border: none color: #FFFFFF &::placeholder color: #FFFFFF底部界面代码 <footer> <div class=“left_menu”> <span class=“iconfont icon-menu3caidan3”></span> <span class=“iconfont icon-tianjiahaoyou”></span> <span class=“iconfont icon-wendang”></span> </div> <div class=“pull-right”> <span class=“iconfont icon-live_icon”></span> <span class=“iconfont icon-shipin1”></span> <span class=“iconfont icon-yinle”></span> <span class=“iconfont icon-anquan”></span> <span class=“iconfont icon-tubiaozhizuomobanyihuifu-"></span> </div> </footer>css代码 footer border-radius: 0 0 4px 4px height: 40px line-height: 40px position: absolute bottom: 0 width: 100% display: flex color: #333 border-top: 1px solid #BDD0DB .pull-right margin-left: auto span display: inline-block width: 30px height: 40px text-align: center font-size: 18px &:hover background-color: #BDD0DB最后效果比对qq给main 加一个背景就差不多了 其实qq主界面的背景色是一整个图 然而我们并没有采取这种方式由于天色已晚 所以先睡觉了 明天有时间继续更新版权声明本文只学习electron使用 不做任何商业用途,文章中使用的腾讯qq相关图片和相关Logo都作为学习使用,如果侵犯了腾讯的相关权益,请联系作者删除! ...

October 26, 2018 · 3 min · jiezi

electron 仿制QQ登录界面

首先来看看qq的登录界面:准备开发制作一个窗口先主进程代码:import {BrowserWindow, webContents, app, ipcMain} from ’electron’LoginWindow(); //暂时调用ipcMain.on(‘quitApp’, () => { app.quit();});function LoginWindow() { const loginURL = process.env.NODE_ENV === ‘development’ ? http://localhost:9080/#/login : file://${__dirname}/index.html/#/login; const loginWindow = new BrowserWindow({ width: 430, height: 328, alwaysOnTop: true, modal: true, frame: false, darkTheme: true, resizable: false, minimizable: false, maximizable: false, transparent: true, webPreferences: { devTools: false, } }); loginWindow.setMenu(null); loginWindow.loadURL(loginURL);}界面基本布局我们先大概做一个这样的界面页面代码:<template> <div class=“mainWindow”> <header class=“header”></header> <main> <div class=“bg”></div> <div class=“body”></div> </main> <footer class=“footer”></footer> </div></template><script> import ‘@/assets/css/login.css’ export default { }</script>样式代码:/*取消全部的外边距和内边距 / { padding: 0; margin: 0;}/设置窗口的样式/.mainWindow { cursor: pointer; /设置手型/ border: 1px solid red; /加一个边框 调试样式 最后要删除或者更改/ width: 428px; /设置宽度 必须要和主进程中设置的一样 不能大于主进程中设置的宽度 否则会出现滚动条/ height: 326px; /设置高度 必须要和主进程中设置的一样 不能大于主进程中设置的高度 否则会出现滚动条/ position: relative; /设置为相对定位/ border-radius: 4px; /设置圆角/}/*header的样式 header中只会有一个关闭按钮 处于右上角 /.mainWindow header.header { position: absolute; /设置绝对定位 因为背景在他下面/ height: 30px; /设置高度/ background: rgba(0, 0, 0, 0.5); /暂时设置的 后面要删除或者更改/ border-radius: 4px 4px 0 0; /给header的左上角 右上角设置圆角 不然会出现很尴尬的页面/ width: 428px; / 因为设置了绝对定位 设置宽度/}/**背景 */.mainWindow main .bg { height: 124px; /设置高度/ width: 428px; /设置宽度 也可以不用设置 因为这个元素没有设置绝对定位 所以默认就是100%/ border-radius: 4px 4px 0 0; /给左上角 右上角设置圆角 不然会出现很尴尬的页面 这里和header重合在一起了/ background: blue; /暂时设置的 后面要删除或者更改/}/**放置表单的元素 */.mainWindow main .body { width: 428px; /设置宽度 也可以不用设置 因为这个元素没有设置绝对定位 所以默认就是100%/ height: 172px; /设置高度 这里的高度是 主窗口(326) - footer(30) - 背景(124) 因为header设置了绝对定位 所以不用关 / background: green; /暂时设置的 后面要删除或者更改/}.mainWindow footer.footer { position: absolute; / 设置绝对定位 要让他处于窗口的最底部/ height: 30px; /设置高度 / background: red; /暂时设置的 后面要删除或者更改/ bottom: 0; /让footer处于底部/ width: 428px; / 因为设置了绝对定位 设置宽度/}窗口拖动注意 不要使用内置的拖动 我们要自己实现!在页面中加入以下代码就可以实现拖动了!data() { return { windowX: 0, windowY: 0, } }, mounted() { let win = this.$electron.remote.getCurrentWindow(); document.addEventListener(‘mousedown’, function (e) { this.windowX = e.x; this.windowY = e.y; document.addEventListener(‘mousemove’, moveEvent); }); document.addEventListener(‘mouseup’, function () { this.windowX = 0; this.windowY = 0; document.removeEventListener(‘mousemove’, moveEvent) }); function moveEvent(e) { win.setPosition(e.screenX - this.windowX, e.screenY - this.windowY) } }设置背景图将css里面的 .bg修改成:.mainWindow main .bg { height: 124px; /设置高度/ width: 428px; /设置宽度 也可以不用设置 因为这个元素没有设置绝对定位 所以默认就是100%/ border-radius: 4px 4px 0 0; /给左上角 右上角设置圆角 不然会出现很尴尬的页面 这里和header重合在一起了/ background: url("../images/login-back.gif") 10px; background-size: 100%;}完成之后效果如如下:制作顶部顶部的logo和最小化就不做了 只做一个关闭的按钮去阿里巴巴图标库下载字体文件之后放到assets/fonts目录中在页面中加入: import ‘@/assets/fonts/iconfont.css’header代码: <header class=“header”> <span class=“iconfont icon-guanbi1”></span> </header>css文件注意 在css .mainWindow header.header 添加由于我背景图的关系 按钮可能不太明显 这问题不大 大家可以自己换一个图!background: rgba(255, 255, 255, 0.2); /暂时设置的 后面要删除或者更改/text-align: right;.mainWindow header.header span{ display: inline-block; height: 30px; width:30px; text-align: center; line-height: 30px; color:#E4393c;}.mainWindow header.header span:hover{ background-color: rgba(255,255,255,0.6);}制作表单页表单界面代码创建一个子组件 login.vue 写入如下代码:<template> <div class=“form”> <form> <div class=“form_item”><i class=“iconfont icon-1zhanghu”></i><input type=“text”></div> <div class=“form_item”><i class=“iconfont icon-mima1”></i><input type=“password”></div> </form> <div class=“buttons”> <button>登录</button> </div> </div></template><script> export default { name: “login” }</script>表单页css需要将 .mainWindow main .body 的背景颜色调成#FFFFFF.form form{ padding:10px 90px 0 90px;}.form_item{ height: 40px; position: relative;}.form_item i.iconfont{ position: absolute; top:5px;}.form_item input{ outline: none; border:none; padding-left: 20px; font-size: 16px; width: 230px; height: 30px; border-bottom: 1px solid #CCC;}.buttons{ text-align: center;}.buttons button{ background-color: #CF000E; border: none; width: 250px; color: #FFFFFF; height: 35px; cursor: pointer; font-size: 14px; border-radius: 4px; outline: none;}效果最后看到是这样的复选框美化组件代码<div class=“login_options”> <label><div class=“option_item”><input type=“checkbox”><span class=“checked”><img src="@/assets/images/checked.png" alt=""></span></div><i class=“text”>自动登录</i></label> <label><div class=“option_item”><input type=“checkbox”><span class=“checked”><img src="@/assets/images/checked.png" alt=""></span></div><i class=“text”>记住密码</i></label> <i class=“text”>忘记密码</i></div>css代码.login_options{ margin-bottom: 10px; margin-top: 5px;}.login_options .option_item { display: inline-block; width: 13px; height: 13px; margin-right: 5px; position: relative; border: 1px solid orange; vertical-align: middle; cursor: pointer; top: -2px;}.login_options .option_item input { opacity: 0;}.login_options i.text{ display: inline-block; margin-right: 14px; font-size: 13px; font-style: normal;}.login_options .option_item span.checked { position: absolute; top: -4px; right: -3px; font-weight: bold; display: inline-block; width: 20px; height: 20px; cursor: pointer;}.option_item span.checked img { width: 100%; height: 100%;}input[type=“checkbox”] + span { opacity: 0;}input[type=“checkbox”]:checked + span { opacity: 1;}效果注册页面我们改进一点 因为qq的注册是一个连接到web页面去申请qq号码的 不过我做的是点击注册将界面切换到注册界面,只不过是在写注册界面代码之前先将父组件种的login注释掉备用 (别删除哦) 在父组件中引入Register组件注册的逻辑是这样的 在注册界面输入手机号和图形验证码 获取到短信验证码输入之后跳转到下一步输入密码如果将全部的逻辑写到一个组件中会导致太长 虽然有办法解决 但是之后使用动画就很难看了!界面代码<template> <div class=“form”> <form> <div class=“form_item”><i class=“iconfont icon-phone_icon”></i><input type=“text”></div> <div class=“form_item”> <i class=“iconfont icon-yanzhengma2”></i> <input type=“password”> <div class=“captcha”> <img src="@/assets/images/captcha.png" alt=""> </div> </div> <div class=“form_item”> <i class=“iconfont icon-yanzhengma5”></i> <input type=“password”> <div class=“send_sms_captcha”><button>获取短信验证码</button></div> </div> </form> <div class=“buttons”> <button>下一步</button> </div> </div></template><script> export default { name: “register” }</script>界面Css代码.captcha { position: absolute; width: 120px; height: 30px; top: -2px; right: 0;}.captcha img { width: 100%; height: 100%;}.send_sms_captcha { position: absolute; top: -2px; right: 0;}.send_sms_captcha button{ width:120px; height: 30px; border:none; outline: none; cursor: pointer; border-radius: 4px;}父组件代码部分代码:<main> <div class=“bg”></div> <div class=“body”> <!–<Login></Login>–> <Register></Register> </div> </main>效果注册步骤2界面代码<template> <div class=“form”> <form> <div class=“form_item”><i class=“iconfont icon-zaicishurumima”></i><input type=“text”></div> <div class=“form_item”><i class=“iconfont icon-mima1”></i><input type=“password”></div> <div class=“login_options” style=“text-align: center”> <label><div class=“option_item”><input type=“checkbox”><span class=“checked”><img src="@/assets/images/checked.png" alt=""></span></div><i class=“text”>自动登录</i></label> <label><div class=“option_item”><input type=“checkbox”><span class=“checked”><img src="@/assets/images/checked.png" alt=""></span></div><i class=“text”>记住密码</i></label> </div> </form> <div class=“buttons”> <button>登录</button> </div> </div></template><script> export default { name: “steps2” }</script>展示footer代码jie简介在上面中footer里面显示了注册账号其实这只是暂时的方案 为了方便截图首先来分析一下 在登录页面的时候在底部显示注册账号 在注册第一步的时候在底部左侧显示已经账号,在第二步骤的时候显示返回上一步我们有很多办法在子组件通知父组件去显示不同的文字作者给出两个方案:1: 通过子组件给父组件传值 2: 使用vuex3: 将footer拆分到各个组件中我们代码中使用拆分就行了 比较简单点将父组件的footer删除往组件login.vue steps1.vue steps2.vue 组件中加入footerlogin.vue:<template> <div class=“form”> <form> <div class=“form_item”><i class=“iconfont icon-1zhanghu”></i><input type=“text”></div> <div class=“form_item”><i class=“iconfont icon-mima1”></i><input type=“password”></div> <div class=“login_options”> <label><div class=“option_item”><input type=“checkbox”><span class=“checked”><img src="@/assets/images/checked.png" alt=""></span></div><i class=“text”>自动登录</i></label> <label><div class=“option_item”><input type=“checkbox”><span class=“checked”><img src="@/assets/images/checked.png" alt=""></span></div><i class=“text”>记住密码</i></label> <i class=“text”>忘记密码</i> </div> </form> <div class=“buttons”> <button>登录</button> </div> <footer class=“footer”> <span @click=“toggleWindow”>注册账号</span> </footer> </div></template><script> export default { name: “login”, methods:{ toggleWindow(){ this.$store.dispatch(’toggleLogin’); } } }</script>steps1.vue<template> <div class=“form”> <form> <div class=“form_item”><i class=“iconfont icon-phone_icon”></i><input type=“text”></div> <div class=“form_item”> <i class=“iconfont icon-yanzhengma2”></i> <input type=“password”> <div class=“captcha”> <img src="@/assets/images/captcha.png" alt=""> </div> </div> <div class=“form_item”> <i class=“iconfont icon-yanzhengma5”></i> <input type=“password”> <div class=“send_sms_captcha”><button>获取短信验证码</button></div> </div> </form> <div class=“buttons”> <button @click=“toggleSteps”>下一步</button> </div> <footer class=“footer”> <span @click=“toggleWindow”>已有账号</span> </footer> </div></template><script> export default { name: “steps1”, methods:{ toggleWindow(){ this.$store.dispatch(’toggleLogin’); }, toggleSteps(){ this.$store.dispatch(’toggleSteps’); }, } }</script>steps2.vue<template> <div class=“form”> <form> <div class=“form_item”><i class=“iconfont icon-zaicishurumima”></i><input type=“text”></div> <div class=“form_item”><i class=“iconfont icon-mima1”></i><input type=“password”></div> <div class=“login_options” style=“text-align: center”> <label><div class=“option_item”><input type=“checkbox”><span class=“checked”><img src="@/assets/images/checked.png" alt=""></span></div><i class=“text”>立即登录</i></label> <label><div class=“option_item”><input type=“checkbox”><span class=“checked”><img src="@/assets/images/checked.png" alt=""></span></div><i class=“text”>记住密码</i></label> </div> </form> <div class=“buttons”> <button>注册</button> </div> <footer class=“footer”> <span @click=“toggleSteps”>返回上一步</span> </footer> </div></template><script> export default { name: “steps2”, methods:{ toggleSteps(){ this.$store.dispatch(’toggleSteps’); }, } }</script>vuex 代码const state = { steps: true, login: true,};const actions = { toggleSteps: function ({state, commit}) { // state.steps = true; state.steps = !state.steps; }, toggleLogin({state, commit}){ state.login = !state.login; }};export default ({ state, actions});效果展示添加动画效果上面这些完成之后有点单调 尤其是切换的时候 我们可以用到 animateCss animateCss 下载地址:https://daneden.github.io/ani…子组件加入: import ‘@/assets/css/animate.css’之后我们在代码中加入效果就行了将父组件改成: <main> <div class=“bg”></div> <transition :duration=“500” :enter-active-class="‘animated ’ + (login ? ‘bounceInRight’ : ‘bounceInLeft’)" :leave-active-class="‘animated ’ + (login ? ‘bounceOutLeft’ : ‘bounceOutRight’)" > <Login v-if=“login === true” key=“login”></Login> <Register v-else key=“register”></Register> </transition> </main>子组件 register.vue改成: <transition :duration=“500” :enter-active-class="‘animated ’ + (steps ? ‘bounceInRight’ : ‘bounceInLeft’)" :leave-active-class="‘animated ’ + (steps ? ‘bounceOutLeft’ : ‘bounceOutRight’)" > <Steps1 v-if=“steps === true” key=“steps”></Steps1> <Steps2 v-else key=“steps”></Steps2> </transition>修改下css 因为要使用动画就要将main定位才能用加入:.mainWindow main { position: absolute;}效果展示:到这里就差不多了 代码太多没法一一发布上来 如果有需要的可以去github下载或者加QQ群 814270669github地址:https://github.com/lihaotian0…码云地址: https://gitee.com/leehaotian/…我的github账号出了问题 一直登录不上去 所以就先发布到码云了 ...

October 23, 2018 · 5 min · jiezi

electron实现qq快捷登录!

之前本来想不写这个功能的,结果客户死活要qq登录! 实在没办法就写了,顺便写个文章!在写之前有两个问题:1: 打开qq授权页面点击页面中的链接会又打开一个页面! …..2: 授权之后是否成功很难去判断 不过脑海中有一个想法就是,electron就是一个类似于浏览器一样,既然是浏览器那肯定可以阻止链接的点击 也可以判断状态!就去啃文档了!!!推荐大家去w3c去看文档 比较全 而且速度较快 文档也比较新: https://www.w3cschool.cn/elec...https://electronjs.org/docs 这里面的响应速度比较慢 里面很多文档都很久了 参数也有失效的!!!言归正传 说qq登录!后端是使用PHP实现的 没什么难度,主要的就是客户端的一些处理!演示放置qq登录按钮<template> <div> <button @click=“qqLogin”>qq登录</button> </div></template><script> export default { name: “home”, mounted() { this.$electron.ipcRenderer.on(‘reply’, (e, data) => { console.log(data) let httpCode = data.request_code[0]; if (httpCode === ‘1’) { alert(data.token[0]) } }) }, methods: { qqLogin() { //请求服务器获取授权页面和参数 this.$http.get(‘xxxxx’) .then((result) => { if (result.data.status === 1) { this.$electron.ipcRenderer.send(‘qqLogin’, {url: result.data.data}); } }) .catch() }, } }</script>问题解决点击a链接会打开一个新窗口解决打开qq授权页面点击页面中的链接会又打开一个窗口的问题 使用webContents 的 new-window 事件 组织默认事件 调用Shell利用默认浏览器打开就行了! loginWindow.webContents.on(’new-window’, (event, url) => { event.preventDefault(); shell.openExternal(url); });授权后是否成功很难去判断到这个问题后我就想到一个词 那就是 Response 和 code 然后就去搜索了嘛 结果在 webContents找到了! did-get-redirect-request 事件 ! 但是我们不能直接使用他 要在点击授权之后再去使用他 loginWindow.webContents.on(‘will-navigate’, (e, url,) => { content.on(‘did-get-response-details’, (e, status, url, originalURL, httpResponseCode, requestMethod, referrer, header) => { if (httpResponseCode === 200) { event.sender.send(‘reply’, header); // loginWindow.close(); } }) });will-navigate事件解释:当用户或 page 想要开始导航的时候发出事件.它可在当 window.location 对象改变或用户点击 page 中的链接的时候发生.当使用 api(如 webContents.loadURL 和 webContents.back) 以编程方式来启动导航的时候,这个事件将不会发出.它也不会在页内跳转发生, 例如点击锚链接或更新 window.location.hash.使用 did-navigate-in-page 事件可以达到目的did-get-response-details 事件解释:当有关请求资源的详细信息可用的时候发出事件. status 标识了 socket链接来下载资源.拿到这两个之后我们就可以写代码啦!在点击授权之后授权页面会跳转到我们服务器的一个回调地址 在里面做一个操作 比如获取用户token乱七八糟的! 之后将生成的token返回给客户端!但是要注意这里服务端返回的数据客户端不能解析 大家可以使用:findInPage 去查询返回的内容!但是我没去这么做 因为 did-get-response-details 事件返回了:status,newURL,originalURL,httpResponseCode,requestMethod,referrer,headers 八个参数 最后我们只需要判断httpResponseCode 是200的时候 将header里面的参数从主进程返回给渲染进程大概的数据是这样的:access-control-allow-credentials:[“true”]access-control-allow-headers:[“token,Origin, X-Requested-With, Content-Type, Accept”]access-control-allow-methods:[“POST,GET,DELETE,PUT”]cache-control:[“no-store, no-cache, must-revalidate”]connection:[“Keep-Alive”]content-type:[“application/json; charset=utf-8”]date:[“Sun, 21 Oct 2018 14:02:20 GMT”]expires:[“Thu, 19 Nov 1981 08:52:00 GMT”]keep-alive:[“timeout=5, max=100”]request_code:[“1”]msg:[“登录成功”]token:[“xxxxxxxx”]pragma:[“no-cache”]server:[“Apache/2.4.23 (Win32) OpenSSL/1.0.2j mod_fcgid/2.3.9”]set-cookie:[“PHPSESSID=6b0esq5jd8vloess2c96ove86s; path=/; HttpOnly”]transfer-encoding:[“chunked”]x-powered-by:[“PHP/7.2.1”]以上参数中 msg request_code token为自定义参数 是服务器代码生成的!能得到这些就好办了!渲染进程拿到header中的token根据 token获取用户信息这之后就简单的很了!!!主进程代码:import {ipcMain, BrowserWindow, shell} from ’electron’ipcMain.on(‘qqLogin’, (event, data) => { const loginWindow = new BrowserWindow({ width: 750, height: 450, resizable: false, minimizable: false, maximizable: false, webPreferences: { devTools: false, } }); loginWindow.setMenu(null); loginWindow.loadURL(data.url); loginWindow.webContents.on(’new-window’, (event, url) => { event.preventDefault(); shell.openExternal(url); }); const content = loginWindow.webContents; content.on(‘will-navigate’, (e, status, url,) => { content.on(‘did-get-response-details’, (e, status, url, originalURL, httpResponseCode, requestMethod, referrer, header) => { if (httpResponseCode === 200) { event.sender.send(‘reply’, header); loginWindow.close(); } }) });});注意点返回的header里面是一个数组 这种写法真是坑爹啊! 还要去写一个 header.token[0] 这种写法有点不喜欢 但是没法子! ...

October 22, 2018 · 2 min · jiezi

使用electron实现百度网盘悬浮窗口功能!

相关依赖里面使用了vuex vue vue-route storeJsstoreJs 用来持久化vuex状态展示介绍说明没有使用electron内置的-webkit-app-region: drag 因为使用他那个有很多问题比如事件无法使用 右键无法使用 以及不能使用手型等!安装安装的时候没有截图 所以就参考下我其他的文章吧storeJs 安装npm install storejs准备写代码配置路由文件export default new Router({ routes: [ {path: ‘/’, name: ‘home’, component: ()=> import(’@/view//home’)}, {path: ‘/suspension’, name: ‘suspension’, component: ()=> import(’@/view/components/suspension’)} ]})写悬浮窗页面页面路径 /src/renderer/view/components/suspension.vue<template> <div id=“suspension”> <div class=“logo”></div> <div class=“content_body”> <div class=“upload”>拖拽上传</div> </div> </div></template><script> export default { name: “suspension”, mounted() { let win = this.$electron.remote.getCurrentWindow(); let biasX = 0; let biasY = 0; let that = this; document.addEventListener(‘mousedown’, function (e) { switch (e.button) { case 0: biasX = e.x; biasY = e.y; document.addEventListener(‘mousemove’, moveEvent); break; case 2: that.$electron.ipcRenderer.send(‘createSuspensionMenu’); break; } }); document.addEventListener(‘mouseup’, function () { biasX = 0; biasY = 0; document.removeEventListener(‘mousemove’, moveEvent) }); function moveEvent(e) { win.setPosition(e.screenX - biasX, e.screenY - biasY) } } }</script><style> * { padding: 0; margin: 0; } .upload { height: 25px; line-height: 25px; font-size: 12px; text-align: center; color: #74A1FA; } .logo { width: 40px; background: #5B9BFE url("../../assets/img/logo@2x.png") no-repeat 2px 3px; background-size: 80%; } .content_body { background-color: #EEF4FE; width: 100%; } #suspension { -webkit-user-select: none; cursor: pointer; overflow: hidden; } #suspension { cursor: pointer !important; height: 25px; border-radius: 4px; display: flex; border: 1px solid #3388FE; }</style>主进程创建悬浮窗页面代码路径: /src/main/window.jsimport {BrowserWindow, ipcMain, screen, Menu, shell, app, webContents} from ’electron’var win = null;const window = BrowserWindow.fromWebContents(webContents.getFocusedWebContents());const winURL = process.env.NODE_ENV === ‘development’ ? http://localhost:9080/#/suspension : file://${__dirname}/index.html/#/suspension;ipcMain.on(‘showSuspensionWindow’, () => { if (win) { if (win.isVisible()) { createSuspensionWindow(); } else { win.showInactive(); } } else { createSuspensionWindow(); }});ipcMain.on(‘createSuspensionMenu’, (e) => { const rightM = Menu.buildFromTemplate([ {label: ‘开始全部任务’, enabled: false}, {label: ‘暂停全部任务’, enabled: false}, {label: ‘本次传输完自动关机’}, {type: ‘separator’}, { label: ‘隐藏悬浮窗’, click: () => { window.webContents.send(‘hideSuspension’, false); win.hide() } }, {type: ‘separator’}, { label: ‘加入qq群’, click: () => { shell.openExternal(’tencent://groupwpa/?subcmd=all&param=7B2267726F757055696E223A3831343237303636392C2274696D655374616D70223A313533393531303138387D0A’); } }, { label: ‘GitHub地址’, click: () => { shell.openExternal(‘https://github.com/lihaotian0607/auth’); } }, { label: ‘退出软件’, click: () => { app.quit(); } }, ]); rightM.popup({});});function createSuspensionWindow() { win = new BrowserWindow({ width: 107, //悬浮窗口的宽度 比实际DIV的宽度要多2px 因为有1px的边框 height: 27, //悬浮窗口的高度 比实际DIV的高度要多2px 因为有1px的边框 type: ’toolbar’, //创建的窗口类型为工具栏窗口 frame: false, //要创建无边框窗口 resizable: false, //禁止窗口大小缩放 show: false, //先不让窗口显示 webPreferences: { devTools: false //关闭调试工具 }, transparent: true, //设置透明 alwaysOnTop: true, //窗口是否总是显示在其他窗口之前 }); const size = screen.getPrimaryDisplay().workAreaSize; //获取显示器的宽高 const winSize = win.getSize(); //获取窗口宽高 //设置窗口的位置 注意x轴要桌面的宽度 - 窗口的宽度 win.setPosition(size.width - winSize[0], 100); win.loadURL(winURL); win.once(‘ready-to-show’, () => { win.show() }); win.on(‘close’, () => { win = null; })}ipcMain.on(‘hideSuspensionWindow’, () => { if (win) { win.hide(); }});store文件路径: /src/renderer/store/modules/suspension.jsimport storejs from ‘storejs’const state = { show: storejs.get(‘showSuspension’)};const actions = { showSuspension: function ({state, commit}) { let status = true; storejs.set(‘showSuspension’, status); state.show = status; }, hideSuspension: function ({state, commit}) { let status = false; storejs.set(‘showSuspension’, status); state.show = status; },};export default ({ state, actions});版权说明里面使用的百度的图标以及UI作为学习使用,请不要作为商业用途!遗留问题在软件关闭之后重启会导致悬浮窗口的位置重置 也曾尝试在主进程中使用store.js 但是不能用!如果想解决这个问题 可以在渲染进程中将拖动的最后坐标保存到storejs中在渲染进程给主进程发送异步消息的时候将坐标携带进去 也可以使用nedb在主进程中存储坐标!github地址使用electron制作百度网盘客户端: https://github.com/lihaotian0…使用electron制作百度网盘悬浮窗: https://github.com/lihaotian0…目前这个开源代码中没有悬浮窗 有时间了会加上去!!! ...

October 22, 2018 · 2 min · jiezi

NSIS 打包 Electron 生成exe安装包

每次文章都从0开始从搭建开始 使用的是electron-vue 毕竟方便一点 如果只想安装electron 请参见我的另一个文章https://segmentfault.com/a/11…开发目录: F:lee`开发环境: windows10IDE: phpstorm安装electronvue init simulatedgreg/electron-vue project3cd project1npm install //第一次安装的伙伴需要翻墙 如何翻墙请参加另一个文章(好像被和谐了 那就+我们的交流群 814270669 吧!)编写一个页面使用IDE打开随便编写一个页面 使用npm 构建安装包npm run build安装程序制作下载NSIS软件,安装下载地址:https://pan.baidu.com/s/1HrZz…下载完毕打开 下一步 下一步 就行了 傻瓜式安装NSIS新建脚本点击软件左上角文件->选择新建脚本(向导)到应用程序信息这里 填写的应用程序名称必须和你package.json里面配置的一样 否则你有自动更新的时候会安装一个另一个程序!这里选择图标就行了这里暂时默认就行了 后面出一个文章详细介绍这里F:\lee\project3\build\win-unpacked\project3.exe主程序就是 buildwin-unpacked的exe文件选择 F:\lee\project3\build\win-unpacked编译脚本终于到了编译脚本了 如果按照上面的步骤执行 到这步会自动编译并且运行 如果没有自动编译点击顶部菜单栏的编译按钮编译过程可能稍微有点长1-3分钟吧 编译完成之后会自动运行安装程序友情提示杀软报毒electron做的软件会被某流氓杀软报毒 没办法解决 在这里给出一个解决办法安装程序检测360是否运行 如果在运行就禁止安装其中使用到一个dll插件 (FindProcDLL.dll)官方下载地址:http://nsis.sourceforge.net/F…作者提供的下载地址:https://pan.baidu.com/s/1EpJa…下载完毕之后 放到NSIS目录下的 VNISEdit\Plugins 目录中如果不知道目录 那就在桌面 右击VNISEdit 编译环境 选择打开所在目录 就可以看到了在脚本最后加一句编译完成后会后些方法:一个是un.onInit ->卸载程序一个是un.onUninstSuccess -> 卸载成功提示.onInit 安装程序初始化# 检测360杀毒软件是否在运行Function .onInitFindProcDLL::FindProc “360tray.exe” Pop $R0 IntCmp $R0 1 0 no_run MessageBox MB_ICONSTOP “安装程序检测到360流氓软件正在运行,请退出程序后重试!” Quit no_run:FunctionEnd由于我电脑没有装360 所以我使用qq 来做演示# 检测电脑管家是否在运行Function .onInitFindProcDLL::FindProc “QQ.exe” Pop $R0 IntCmp $R0 1 0 no_run MessageBox MB_ICONSTOP “安装程序检测到qq流氓软件正在运行,请退出程序后重试!” Quit no_run:FunctionEndNSIS运行必须为管理员请以管理员身份运行VNISEdit 编译环境 不然会终止编译并且有一个警告 好像是需要提级 什么什么的! ...

October 17, 2018 · 1 min · jiezi

electron-builder打包见解

Electron-builder打包详解开发electron客户端程序,打包是绕不开的问题。下面就我在工作中的经验以及目前对electron-builder的了解来分享一些心得。基本概念官网的定义A complete solution to package and build a ready for distribution Electron app for macOS, Windows and Linux with “auto update” support out of the box.关于electron和electron-builder的基础部分这篇文章就跳过了,有兴趣的话可以看这篇文章如何使用builder的使用和配置都是很简单的builder配置有两种方式package.json中直接配置使用(比较常用,我们下面着重来讲这个)指定electron-builder.yml文件demo地址会在文章末尾给出(demo项目中electron使用得是V2.0.7版本,目前更高得是2.0.8版本)。下面是一个简单的package.js中带注释的配置基础配置"build": { // 这里是electron-builder的配置 “productName”:“xxxx”,//项目名 这也是生成的exe文件的前缀名 “appId”: “com.xxx.xxxxx”,//包名 “copyright”:“xxxx”,//版权 信息 “directories”: { // 输出文件夹 “output”: “build” }, // windows相关的配置 “win”: { “icon”: “xxx/icon.ico”//图标路径 } }在配置文件中加入以上的文件之后就可以打包出来简单的<font clolor=“red”>文件夹</font>,文件夹肯定不是我们想要的东西。下一步我们来继续讲别的配置。打包目标配置要打包成安装程序的话我们有两种方式,使用NSIS工具对我们的文件夹再进行一次打包,打包成exe通过electron-builder的nsis直接打包成exe,配置如下"win": { // 更改build下选项 “icon”: “build/icons/aims.ico”, “target”: [ { “target”: “nsis” // 我们要的目标安装包 } ] },其他平台配置 “dmg”: { // macOSdmg “contents”: [ … ] }, “mac”: { // mac “icon”: “build/icons/icon.icns” }, “linux”: { // linux “icon”: “build/icons” }nsis配置这个要详细的讲一下,这个nsis的配置指的是安装过程的配置,其实还是很重要的,如果不配置nsis那么应用程序就会自动的安装在C盘。没有用户选择的余地,这样肯定是不行的关于nsis的配置是在build中nsis这个选项中进行配置,下面是部分nsis配置"nsis": { “oneClick”: false, // 是否一键安装 “allowElevation”: true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。 “allowToChangeInstallationDirectory”: true, // 允许修改安装目录 “installerIcon”: “./build/icons/aaa.ico”,// 安装图标 “uninstallerIcon”: “./build/icons/bbb.ico”,//卸载图标 “installerHeaderIcon”: “./build/icons/aaa.ico”, // 安装时头部图标 “createDesktopShortcut”: true, // 创建桌面图标 “createStartMenuShortcut”: true,// 创建开始菜单图标 “shortcutName”: “xxxx”, // 图标名称 “include”: “build/script/installer.nsh”, // 包含的自定义nsis脚本 这个对于构建需求严格得安装过程相当有用。 “script” : “build/script/installer.nsh” // NSIS脚本的路径,用于自定义安装程序。 默认为build / installer.nsi },关于include 和 script 到底选择哪一个 ?在对个性化安装过程需求并不复杂,只是需要修改一下安装位置,卸载提示等等的简单操作建议使用include配置,如果你需要炫酷的安装过程,建议使用script进行完全自定义。NSIS对于处理安装包这种东西,功能非常的强大。但是学习起来并不比一门高级语言要容易。其中的奥秘还要各位大佬自行探索这里上一些学习资源NSIS初级篇NSIS 打包脚本基础示例脚本NSIS论坛关于操作系统的配置主要是windows中64和32位的配置CLI参数electron-builder –ia32 // 32位electron-builder // 64位(默认)nsis中配置"win": { “icon”: “build/icons/aims.ico”, “target”: [ { “target”: “nsis”, “arch”: [ // 这个意思是打出来32 bit + 64 bit的包,但是要注意:这样打包出来的安装包体积比较大,所以建议直接打32的安装包。 “x64”, “ia32” ] } ]}更新配置下面这个是给更新用的配置,主要是为了生成lastest.yaml配置文件"publish": [ { “provider”: “generic”, // 服务器提供商 也可以是GitHub等等 “url”: “http://xxxxx/” // 服务器地址 }],完整配置基本上可用的完整的配置"build": { “productName”:“xxxx”,//项目名 这也是生成的exe文件的前缀名 “appId”: “com.leon.xxxxx”,//包名 “copyright”:“xxxx”,//版权 信息 “directories”: { // 输出文件夹 “output”: “build” }, “nsis”: { “oneClick”: false, // 是否一键安装 “allowElevation”: true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。 “allowToChangeInstallationDirectory”: true, // 允许修改安装目录 “installerIcon”: “./build/icons/aaa.ico”,// 安装图标 “uninstallerIcon”: “./build/icons/bbb.ico”,//卸载图标 “installerHeaderIcon”: “./build/icons/aaa.ico”, // 安装时头部图标 “createDesktopShortcut”: true, // 创建桌面图标 “createStartMenuShortcut”: true,// 创建开始菜单图标 “shortcutName”: “xxxx”, // 图标名称 “include”: “build/script/installer.nsh”, // 包含的自定义nsis脚本 }, “publish”: [ { “provider”: “generic”, // 服务器提供商 也可以是GitHub等等 “url”: “http://xxxxx/” // 服务器地址 } ], “files”: [ “dist/electron/**/*” ], “dmg”: { “contents”: [ { “x”: 410, “y”: 150, “type”: “link”, “path”: “/Applications” }, { “x”: 130, “y”: 150, “type”: “file” } ] }, “mac”: { “icon”: “build/icons/icon.icns” }, “win”: { “icon”: “build/icons/aims.ico”, “target”: [ { “target”: “nsis”, “arch”: [ “ia32” ] } ] }, “linux”: { “icon”: “build/icons” } }命令行参数(CLI)Commands(命令): electron-builder build 构建命名 [default] electron-builder install-app-deps 下载app依赖 electron-builder node-gyp-rebuild 重建自己的本机代码 electron-builder create-self-signed-cert 为Windows应用程序创建自签名代码签名证书 electron-builder start 使用electronic-webpack在开发模式下运行应用程序(须臾要electron-webpack模块支持)Building(构建参数): –mac, -m, -o, –macos Build for macOS, [array] –linux, -l Build for Linux [array] –win, -w, –windows Build for Windows [array] –x64 Build for x64 (64位安装包) [boolean] –ia32 Build for ia32(32位安装包) [boolean] –armv7l Build for armv7l [boolean] –arm64 Build for arm64 [boolean] –dir Build unpacked dir. Useful to test. [boolean] –prepackaged, –pd 预打包应用程序的路径(以可分发的格式打包) –projectDir, –project 项目目录的路径。 默认为当前工作目录。 –config, -c 配置文件路径。 默认为electron-builder.yml(或js,或js5)Publishing(发布): –publish, -p 发布到GitHub Releases [choices: “onTag”, “onTagOrDraft”, “always”, “never”, undefined]<font color=“red”>Deprecated(废弃):</font> –draft 请改为在GitHub发布选项中设置releaseType [boolean] –prerelease 请改为在GitHub发布选项中设置releaseType [boolean] –platform 目标平台 (请更改为选项 –mac, –win or –linux) [choices: “mac”, “win”, “linux”, “darwin”, “win32”, “all”, undefined] –arch 目标arch (请更改为选项 –x64 or –ia32) [choices: “ia32”, “x64”, “armv7l”, “arm64”, “all”, undefined]Other(其他): –help Show help [boolean] –version Show version number [boolean]Examples(例子): electron-builder -mwl 为macOS,Windows和Linux构建(同时构建) electron-builder –linux deb tar.xz 为Linux构建deb和tar.xz electron-builder -c.extraMetadata.foo=bar 将package.js属性foo设置为bar electron-builder –config.nsis.unicode=false 为NSIS配置unicode选项 TargetConfiguration(构建目标配置):target: String - 目标名称,例如snap.arch “x64” | “ia32” | “armv7l” | “arm64”> | “x64” | “ia32” | “armv7l” | “arm64” -arch支持列表总结electron-builder是一个简单又强大的库。反正我是很服Demo地址原文地址 如果觉得有用得话给个⭐吧 ...

October 16, 2018 · 3 min · jiezi

electron 自动更新以及手动更新

从搭建开始 使用的是electron-vue 毕竟方便一点 如果只想安装electron 请参见我的另一个文章 https://segmentfault.com/a/11…首先安装Electron:vue init simulatedgreg/electron-vue project1cd project1npm install //第一次安装的伙伴需要翻墙 如何翻墙请参加另一个文章(好像被和谐了 那就+我们的交流群吧!)安装的时候安装了 vue electron vue-router 不安装 vuex 打包选择的是: electron-builder 下次有时间再扯electron-packager安装完毕之后启动运行npm run dev构建页面更新进度页面将他写成组件 update.vue<template> <transition name=“fade”> <div v-if=“show”> <div class=“modal”></div> <div class=“update”> <div class=“header”><h2>应用更新</h2><i class=“close” @click=“close”></i></div> <div class=“body”> <p>更新进度</p> <p class=“percentage”>10%</p> <div class=“progress”> <div class=“length”></div> </div> </div> </div> </div> </transition></template><script> export default { name: “update”, methods: { close() { this.$emit(‘update:show’, false) } }, props: { show: { type: Boolean, required: true, default: false } } }</script><style> .fade-enter-active, .fade-leave-active { transition: opacity .5s; } .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 / { opacity: 0; } .modal { position: fixed; left: 0; top: 0; width: 100%; height: 100%; opacity: .4; background: #000; } .update { width: 400px; height: 180px; background-color: #FFFFFF; border-radius: 10px; border: 1px solid #CCC; position: absolute; top: 40%; margin-top: -90px; left: 50%; margin-left: -200px; box-shadow: #FFFFFF 0 0 10px; } .update .header i.close { display: inline-block; position: absolute; top: 11px; right: 12px; width: 20px; height: 20px; background-image: url("../assets/img/close.png"); background-size: 100%; cursor: pointer; } .update .header { border-bottom: 1px solid #ccc; height: 40px; line-height: 40px; } .update .header h2 { text-align: center; font-size: 20px; } .update .body { padding-top: 20px; text-align: center; } .update .body .percentage { margin-top: 20px; } .update .body .progress { width: 350px; height: 30px; border: 1px solid #CCCCCC; border-radius: 8px; margin: 10px auto; } .update .body .progress .length { background-color: #E4393c; border-radius: 8px; width: 10px; height: 30px; }</style>安装模块安装 electron-updater 包模块npm install electron-updater –save修改package.json加入以下代码 “publish”: [ { “provider”: “generic”, “url”: “http://lee.com/app/update” } ],配置更新服务器我们的更新服务器是本地虚拟主机 以apache为例配置apache服务器我本地使用的是集成环境 很简单的操作 要是大家使用自定义安装的 往httpd-vhosts.conf里面添加配置就可以了我们的域名是lee.com修改hosts文件修改 hosts文件 往里面添加 文件地址在 C:WindowsSystem32driversetc目录下127.0.0.1 lee.com核心文件主进程中 主要是handleUpdate方法import {app, BrowserWindow, ipcMain} from ’electron’// 注意这个autoUpdater不是electron中的autoUpdaterimport {autoUpdater} from “electron-updater”/* * Set __static path to static files in production * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html /if (process.env.NODE_ENV !== ‘development’) { global.__static = require(‘path’).join(__dirname, ‘/static’).replace(/\/g, ‘\\’)}let mainWindowconst winURL = process.env.NODE_ENV === ‘development’ ? http://localhost:9080 : file://${__dirname}/index.htmlfunction createWindow() { /* * Initial window options / mainWindow = new BrowserWindow({ height: 563, useContentSize: true, width: 1000 }) mainWindow.loadURL(winURL) mainWindow.on(‘closed’, () => { mainWindow = null });//处理更新操作 function handleUpdate() { const returnData = { error: {status: -1, msg: ‘检测更新查询异常’}, checking: {status: 0, msg: ‘正在检查应用程序更新’}, updateAva: {status: 1, msg: ‘检测到新版本,正在下载,请稍后’}, updateNotAva: {status: -1, msg: ‘您现在使用的版本为最新版本,无需更新!’}, }; //和之前package.json配置的一样 autoUpdater.setFeedURL(‘http://xxx.com/app/update'); //更新错误 autoUpdater.on(’error’, function (error) { sendUpdateMessage(returnData.error) }); //检查中 autoUpdater.on(‘checking-for-update’, function () { sendUpdateMessage(returnData.checking) }); //发现新版本 autoUpdater.on(‘update-available’, function (info) { sendUpdateMessage(returnData.updateAva) }); //当前版本为最新版本 autoUpdater.on(‘update-not-available’, function (info) { setTimeout(function () { sendUpdateMessage(returnData.updateNotAva) }, 1000); }); // 更新下载进度事件 autoUpdater.on(‘download-progress’, function (progressObj) { mainWindow.webContents.send(‘downloadProgress’, progressObj) }); autoUpdater.on(‘update-downloaded’, function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) { ipcMain.on(‘isUpdateNow’, (e, arg) => { //some code here to handle event autoUpdater.quitAndInstall(); }); // win.webContents.send(‘isUpdateNow’) }); //执行自动更新检查 autoUpdater.checkForUpdates(); } handleUpdate();// 通过main进程发送事件给renderer进程,提示更新信息 function sendUpdateMessage(text) { mainWindow.webContents.send(‘message’, text) } ipcMain.on(“checkForUpdate”, (event, data) => { console.log(‘执行自动更新检查!!!’); // event.sender.send(‘reply’, ‘hi lee my name is yuan, age is 17’); autoUpdater.checkForUpdates(); });}app.on(‘ready’, createWindow)app.on(‘window-all-closed’, () => { if (process.platform !== ‘darwin’) { app.quit() }});app.on(‘activate’, () => { if (mainWindow === null) { createWindow() }});更新参数讲解在有更新包的情况下会在主进程中触发下面的方法: autoUpdater.on(‘download-progress’, function (progressObj) { // mainWindow.webContents.send(‘downloadProgress’, progressObj) const winId = BrowserWindow.getFocusedWindow().id; let win = BrowserWindow.fromId(winId); win.webContents.send(‘downloadProgress’, progressObj) });progressObj : { “bytesPerSecond”: 47132710, “delta”: 39780007, “percent”: 100, “total”: 39780007, “transferred”: 39780007 } bytesPerSecond: bps/s //传送速率percent : 百分比 //我们需要这个就可以了total : 总大小transferred: 已经下载发布更新将新的安装包和latest.yml 放到对应的目录下 系统会自动去检测版本 如果有新版本会下载的!!检测更新创建触发更新的组件 <div><h2>你好 我是1.2.4</h2> <button @click=“updateApp” style=“width:100px;height: 40px;">更新</button> <Update :show.sync=“show” :percent=“percent”></Update> </div></template><script> import Update from “@/components/update”; export default { name: “index”, components: {Update}, data() { return { percent: 0, show: false } }, mounted() { //更新进度 this.$electron.ipcRenderer.on(‘downloadProgress’, (event, data) => { this.percent = (data.percent).toFixed(2); if (data.percent >= 100) { // this.show = false; } }); /* * 主进程返回的检测状态 */ this.$electron.ipcRenderer.on(‘message’, (event, data) => { switch (data.status) { case -1: this.$Message.error(data.msg); break; case 0: this.$Message.loading(data.msg); break; case 1: this.show = true; break; } }); }, methods: { updateApp() { this.$electron.ipcRenderer.send(‘checkForUpdate’, ‘asdad’) } } }</script>总结由于我的虚拟机是在本地 所以下载速度超快后来我将更新地址切换到远程服务器 下面是操作截图 ...

October 14, 2018 · 3 min · jiezi