接上一篇。现在我们已经完成了Angular和Electron的交互,在渲染进程中执行的任何动作都可以及时发送到主进程进行分析和存储,然后得到它的反馈,渲染进程会根据情况做出合理的响应到反馈。今天我们需要完成剩下的登录和与主进程交互的功能模块。从事件中重新加载窗口由于我们需要通过响应事件来替换窗口对象,因此我们至少需要窗口对象的一个??函数。当然,这些功能应该被提取为一个服务。其次,我们还要考虑到窗口对象句柄的存储和回收,在更换对象的时候或者在合适的时候改变这些窗口对象的句柄。鉴于这些,我们可以在原有代码的基础上设计一个通用类。暂且调用操作window对象Screen的类(有些过时,但需要和window区分开来),可以在根目录下index.js调用,也可以在任意api函数中调用,也就是说,无论如何,Screen的实例都应该只有一个,这样就可以将window对象的句柄缓存在内存中,供调用者操作。结合我们前面写的index.js文件,再想想Screen这个类。它还需要一些被动的方法来响应窗口的最大化、最小化、关闭、激活等,这些操作可以抽象成固定的参数。功能,因此我们还将向Screen类添加一些静态方法。具体如下://browser/screen/login.jsconst{app,BrowserWindow}=require('electron')module.exports=newclassLogin{constructor(){}open(url){constwin=newBrowserWindow({宽度:700,高度:500,显示:假,框架:假,可调整大小:真});win.loadURL(url)win.webContents.openDevTools()returnwin}}()创建一个Login类打开登录窗口,同理,我们可以将这段代码复制一次,改名为console类,负责打开控制面板。它有几个特点。它接受一个参数url,创建一个window对象并加载,最后返回window对象供外部使用。需要注意的是,这里需要传入loadURL的值,或者命名一个全局变量,将index.js的__dirname存放在根目录下,以简化路径,后面我们做其他事情时会用到它。现在可以创建Screen类了://browser/screen/index.jsconstlogin=require('./login')constconsole=require('./console')constwindowList={login:login,console:console}模块。exports=newclassScreen{constructor(){this.win=nullthis.baseUrl=''}staticshow(win){win.show()win.focus()}//打开一个窗口通过打开登录窗口默认打开(winName='login'){if(!windowList[winName])return;this.win=windowList[winName].open(this.baseUrl)this.win.on('closed',()=>this.win=null)this.win.on('ready-to-show',()=>Screen.show(this.win))}setBaseUrl(baseUrl){this.baseUrl=baseUrlreturnthis}activate(){this.win===null&&this.open()}}()windowList用于检查传入名称是否有效。这一步看起来有点多余,但它是一个很好的编程习惯。在多人协作的过程中,你在不断完善自己的代码的同时,也可以为别人避免一些错误。类似于防御性编程。setBaseUrl就是我们刚才提到的存储__dirname的函数。看起来整体画面已经完成了,我们回过头来对根目录下的index.js做一些优化://根目录下的index.jsconst{app,BrowserWindow}=require('electron')constscreen=require('./browser/screen')require('./browser/ipc/index')consturl=`file://${__dirname}/dist/index.html`app.on('ready',_=>screen.setBaseUrl(url).open())app.on('window-all-closed',_=>process.platform!=='darwin'&&app.quit())app.on('activate',_=screen.activate())怎么样?现在看起来还行,现在只需要在ipc/api下的具体文件中使用screen.open('console')打开一个新窗口,Angular端收到通知后也跳转到路由,负责new页。这是一个帮助大家理解应用程序如何工作的示例。在生产环境中,首先应该使用成熟的框架或库来解决这些问题,比如electron-router。重新加载窗口的重构仍然存在一些小问题。登录成功后,我们让Electron打开一个新窗口,但无论如何这是一个不优雅的解决方案。弹出一个新窗口意味着原来的窗口需要立即消失。在注销时,将再次打开一个新的登录窗口。我们可以更新现有的业务逻辑,让路由控制回到Angular自己的手中。同时,Electron在适当的时候对窗口的大小和位置进行合理的改变。现在让我们在Screen类中再添加一个方法://browser/screen/index.js//......setSize(w,h){if(this.win){constbounds=this.win.getBounds();constnewBounds={x:bounds.x-(w-bounds.width)/2,y:bounds.y-(h-bounds.height)/2}this.win.setBounds({x:newBounds.x,y:newBounds.y,width:w,height:h},true)}}虽然调用了setSize方法,但我们实际上改变了窗口的边界,这是合理的,我们总是暴露一个简单的方法,即使有些东西在这里完成,它不受参数变化的影响。每次窗口变化,总能找到一个合理的位置。对于调用者来说,相当于一个setSize。不要急于优化这个功能。后面我们会讨论如何解决配置文件和缓存的问题。届时将用户的自定义设置导入到函数中,使主界面的开启位置与上次关闭的位置保持一致。.甚至我们需要给Angular添加一些session识别路由跳转功能。现在,/browser/ipc/api/index.js又被我们改了,像这样:constscreen=require('../../screen')module.exports={login:(e,user)=>{//todosomethingscreen.setSize(1000,720)e.reply({msg:'ok'})}}一切顺理成章,窗口在MAC上变化并带有一些动画效果,是不是很酷?而且它总能找到最合乎逻辑的地方,看起来更像是一个成熟的应用程序。现在,让我们对Angular应用程序进行一些更改。Angular事件订阅虽然我们可以使用Promise来快速完成这些事情,但是既然开始学习了,我们也不妨学习一些新技术。Rx.js是一个非常有趣的。很多朋友可能听说过其他语言的Reactive模式,所以理解起来并不难。如果你是第一次听到这个术语,不妨先阅读一下这些文档:官方文档翻译另一个不错的翻译是把Promise转化为Observable非常简单。你可以简单的把Rx.js理解为一个使用函数式编程来操作Event的库://src/app/login/login.service.tsimport{Injectable}from'@angular/core'import{IpcRendererService}from'../shared/service/ipcRenderer'import{Observable}from'rxjs/Observable'import'rxjs/Rx'@Injectable()exportclassLoginService{constructor(privateipcRendererService:IpcRendererService){}login(user:any):Observable
