使用Electron编写桌面应用程序是迄今为止我在职业生涯中使用过的最简单,最令人愉快的GUI应用程序工具包。 Electron是Atom,Visual Studio Code,Slack桌面客户端,Postman,GitKraken和Etcher等流行应用程序中使用的工具包。 每个都是功能强大,高性能,跨平台的应用程序,具有丰富的用户体验,为用户提供卓越的功能,每个平台上具有相同的用户界面界面,同时正确集成到平台中。
跨平台GUI工具包经常被嘲笑为速度慢,不能提供与平台的良好集成,或者在GUI功能方面有所妥协。 Electron通过基于谷歌Chrome浏览器的巧妙架构实现这一壮举。 也就是说,Electron包装Chromium的核心,以便您编写Node.js应用程序来管理包含应用程序窗口的一个或多个BrowserWindow对象。 顾名思义, BrowserWindow提供了基于最新版Chrome的完整HTML5,CSS3,JavaScript ES-2016编程环境。
换句话说 - 使用Electron,您可以使用最先进的Web技术来开发桌面GUI应用程序。
一个有趣的因素是,因为Electron基于Chromium,Chrome开发人员工具是内置的。这意味着Electron应用程序开发人员可以轻松获得优秀的Web应用程序调试和检查工具,这些工具已经过前端工程师的全面测试。世界。
Electron的架构有两套流程。 Main进程是前面提到的Node.js应用程序,用于管理应用程序的其余部分。 Renderer进程是前面提到的BrowserWindow实例。 每个都是一个顶级窗口,其中包含通过HTML + CSS + JavaScript代码指定的UI。 这些进程通过进程间通信通道相互通信。
虽然Electron使用Chromium,但安全模型与常规浏览器JavaScript有很大不同。 在Electron BrowserWindow中执行的代码可以访问Node.js模块,并可以访问文件系统。 有一些安全警告: https : //electronjs.org/docs/tutorial/security阅读安全警告非常重要。
要探索Electron,我们将使用您选择的HTML模板构建一个Markdown编辑器,其中包含生成的HTML的实时预览。 UI将使用Vue.js,Bulma / Buefy和ACE编辑器组件的组合构建。
结果可能是这样的应用程序:
目标
左侧是带有Markdown测试文件的ACE编辑器组件,右侧是呈现给HTML的Markdown的预览。 这是该应用程序的核心。 当然,完成的应用程序将需要工具栏,菜单选项和其他一些东西。 我们将完成一个应用程序,以提供一个有用的示例。
GitHub上提供了本文附带的源代码,以帮助阅读: https ://github.com/sourcerer-io/electron-vue-buefy-editor
因为Electron基于Node.js和JavaScript,所以您需要熟悉它们。 可以使用Node.js创建一整套东西。 它是一个代码开发平台,用于在浏览器外部运行JavaScript,主要是在服务器环境中。 像Node.js Web Development这样的书可以让你开始使用Node.js. Electron将Node.js带入桌面应用程序开发领域。
构建编辑器脚手架
随着所有这些部件的组装,存在许多可能使应用程序开发变得困难的复杂性。 感谢各自的团队提供有用的应用程序入门代码,这项任务远没有那么困难。
Electron框架已经将一些复杂性捆绑到一个易于使用的包中。 例如,Electron文档( https://electronjs.org/docs/tutorial/first-app )包含用于启动简单演示应用程序的配方。 遵循这些说明并研究最终的应用程序非常有用。 但是,我们需要其他东西作为起点。
由于目标是使用Vue.js构建此应用程序,因此我们需要一个适用于该工具包的起点。 Vue.js应用程序通常是为网站构建的,所以我们需要一些不同的东西。
在这种情况下,我们想在Electron中运行Vue.js代码。 由于Electron UI是使用Web技术创建的,因此它当然可以支持Vue.js. Electron-Vue框架提供了我们所需的一切,一个支持在电子应用程序中使用Vue.js的构建和打包系统。https://simulatedgreg.gitbooks.io/electron-vue/
首先,安装Vue命令行工具:
<span style="color:rgba(0, 0, 0, 0.84)"> $ sudo npm install -g vue-cli </span>
vue-cli提供的主要功能是下载应用程序模板并设置启动器应用程序。 虽然它有一些内置模板,但它可用于下载第三方模板,包括Electron-Vue。
<span style="color:rgba(0, 0, 0, 0.84)"> $ vue init simulatedgreg / electron-vue electron-vue-buefy-editor </span>
<span style="color:rgba(0, 0, 0, 0.84)"> ? 应用程序名称electron-vue-buefy-editor
? 项目描述电子项目
? 选择要安装vue-electron的Vue插件
? 使用lint和ESLint? 没有
? 使用Karma + Mocha设置单元测试? 没有
? 使用Spectron + Mocha设置端到端测试? 没有
? 你想用什么构建工具? 建设者
? 作者David Herron < <a data-cke-saved-href="mailto:david@davidherron.com" href="mailto:david@davidherron.com" class="markup--anchor markup--pre-anchor">david@davidherron.com</a> > vue-cli·生成“electron-vue-buefy-editor”。 </span>
<span style="color:rgba(0, 0, 0, 0.84)"> - - </span>
<span style="color:rgba(0, 0, 0, 0.84)"> 可以了,好了。 欢迎来到您的新电子项目! </span>
<span style="color:rgba(0, 0, 0, 0.84)"> 请务必查看此样板文档
https://simulatedgreg.gitbooks.io/electron-vue/content/。 </span>
<span style="color:rgba(0, 0, 0, 0.84)"> 下一步: </span>
<span style="color:rgba(0, 0, 0, 0.84)"> $ cd electron-vue-buefy-editor
$ yarn(或`npm install`)
$ yarn run dev(或`npm run dev`) </span>
<span style="color:rgba(0, 0, 0, 0.84)"> 初始项目设置有许多替代方案。 在这个例子中,我们只安装了vue-electron,没有安装ESLint或单元测试支持,并使用电子构建器指定打包应用程序。 您可能更喜欢不同的设置,因此请根据需要回答问题。 </span>
您可以按照这些说明查看基本的Electron-Vue应用程序。
演示Electron-Vue应用程序
选择用于Vue.js的UI工具包组件
Vue.js是用于实现在Web浏览器中运行的组件的框架。 Vue.js应用程序是使用这些组件构建的,理论上可以使用Vue.js以及自定义开发的CSS和JavaScript完全构建UI。 但是,有一些开源项目提供预先烘焙的UI组件,内置响应式Web最佳实践。
由于Bootstrap非常受欢迎,因此首先想到的是使用Bootstrap。 虽然将Bootstrap集成到Vue.js应用程序很容易,但很快就会遇到问题。 Vue.js和jQuery非常不兼容,强烈建议不要在Vue.js中使用Bootstrap或jQuery。 问题是Vue.js期望严格控制DOM操作,因此无法使用jQuery等其他技术进行DOM操作。 有多个项目试图将Bootstrap代码集成为Vue.js组件,但没有一个项目支持Bootstrap 4。
一个广泛推荐的替代方案是Bulma工具包。 不能说这个名字有吸引力,但网站https://bulma.io/为Bulma提供了一个很好的案例,作为一个有价值的UI框架。 有超过100,000名开发人员使用Bulma,它是一个仅限CSS的工具包,使其轻量级,易于与Vue.js集成。 Buefy组件库将Bulma与Vue.js集成: https://buefy.github.io/#/我们将在应用程序中使用Buefy。
Buefy可以使用https://materialdesignicons.com/上设置的Material Design图标。这些图标可作为Node.js模块在https://www.npmjs.com/package/vue-material-design-icons上使用
要做的最后一个UI选择是用于编辑Markdown的组件。 虽然我们可以让用户在常规TextArea组件中编写Markdown,但我们可以做得更好。 例如,Ace编辑器组件为各种编码语言(如JavaScript或C ++或HTML)提供了非常称职的编辑体验。 我们可能希望编辑器能够支持编辑HTML模板,CSS文件或许多其他资源,而Ace编辑器组件可以处理所有这些。 Ace在其网站上有记录: https ://ace.c9.io/ Vue2 Ace Editor组件包用于在Vue.js中使用Ace: https: //github.com/chairuosen/vue2-ace-editor
将Buefy和ACE添加到Electron / Vue.js应用程序
我们已经初始化了一个空白的Vue.js应用程序,并选择了要使用的UI框架。让我们首先将这些组件与空白应用程序集成。
目录结构包括:
目录src/main
包含Main进程的代码,而src/renderer
包含Renderer进程的代码。 您将看到后者是Vue组件的存储位置。
首先是安装包依赖项:
<span style="color:rgba(0, 0, 0, 0.84)"> $ npm install buefy vue2-ace-editor vue-material-design-icons --save </span>
这将安装前面描述的Vue.js组件,以及它们的依赖项,例如Bulma框架和Ace编辑器组件。
编辑index.ejs
,这是Electron-Vue提供的布局模板,以匹配此代码:
<span style="color:rgba(0, 0, 0, 0.84)"> < <strong>html</strong> style =“height:100%;”>
< <strong>头</strong> >
< <strong>meta</strong> charset =“utf-8”>
< <strong>meta</strong> name =“viewport”content =“width = device-width,initial-scale = 1”>
< <strong>title</strong> > electron-vue-buefy-editor </ <strong>title</strong> >
..
</ <strong>head</strong> >
< <strong>body</strong> style =“height:100%;”> .. </ <strong>body</strong> >
</ <strong>html</strong> > </span>
需要修改<html>
和<body>
标记,以便应用程序填充窗口的整个高度。
接下来我们将Buefy带入应用程序,将src/renderer/main.js
的前面部分更改为:
<span style="color:rgba(0, 0, 0, 0.84)"> 从'vue'导入Vue;
从'./App'导入应用程序;
从'./router'导入路由器;
从'buefy'导入Buefy;
import'buefy / lib / buefy.css';
从'util'导入util; </span>
<span style="color:rgba(0, 0, 0, 0.84)"> 从'电子'导入{ipcRenderer}; </span>
<span style="color:rgba(0, 0, 0, 0.84)"> Vue.use(Buefy); </span>
我们将继续对此文件进行更多更改。 这部分将Buefy组件添加到Vue.js.
以这种方式添加Buefy遵循电子应用的最佳实践。 根据Electron文档( https://electronjs.org/docs/tutorial/security )中的Security页面,非常重要的是,不要从第三方Web服务加载代码。 阅读安全页面了解更多详情。 真。这不是一个闲置的建议,去看吧。
ipcRenderer
对象用于从Main进程到Renderer进程的通信。
此时您可以执行npm run dev
- 在开发模式下运行应用程序 - 并且看到没有任何更改。 然而,我们为有趣的事情奠定了基础。
实现编辑器应用程序
接下来,删除src/renderer/components
下的每个文件。 这些文件包含演示屏幕,我们在应用程序中不需要它。
将App.vue
更改为:
<span style="color:rgba(0, 0, 0, 0.84)"> <模板>
<div id =“app”>
<编辑页> </编辑器页>
</ DIV>
</模板> </span>
<span style="color:rgba(0, 0, 0, 0.84)"> <SCRIPT>
从'@ / components / EditorPage'导入EditorPage
export default {
名称:'electron-vue-buefy-editor',
组件: {
EditorPage
}
}
</ SCRIPT> </span>
<span style="color:rgba(0, 0, 0, 0.84)"> <风格>
/ * CSS * /
#app {
身高:100%;
}
</样式> </span>
第一个更改是使用名为EditorPage
的组件作为应用程序的主要部分。 继续下一段,我们将创建该组件。 第二个更改是确保编辑器填充窗口的垂直高度。
Vue.js将其称为单个文件模板 。 还有另一种实现Vue组件的方法,它是可以引用其他文件(包括外部模板或CSS文件)的JavaScript代码。 正如其名称所示,单个文件模板将它们组合在一个文件中。
在src/renderer/components
创建EditorPage.vue
,它将定义EditorPage
组件,并以<template>
和<style>
部分开头:
<span style="color:rgba(0, 0, 0, 0.84)"> < <strong>template</strong> >
< <strong>div</strong> id =“wrapper”>
< <strong>div</strong> id =“editor”class =“columns is-gapless is-mobile”>
< <strong>编辑</strong>
ID =” aceeditor”
REF =” aceeditor”
类=”塔”
V型=”输入”
@初始化=” editorInit”
郎=”降价”
主题=”暮光之城”
宽度=” 500px的”
height =“100%”> </ <strong>editor</strong> >
< <strong>preview-iframe</strong>
ID =” previewor”
类=”塔”
ref =“previewor”> </ <strong>preview-iframe</strong> >
</ <strong>div</strong> >
</ <strong>div</strong> >
</ <strong>template</strong> > </span>
<span style="color:rgba(0, 0, 0, 0.84)"> < <strong>style</strong> scoped>
#wrapper {
身高:100%;
} </span>
<span style="color:rgba(0, 0, 0, 0.84)"> #editor {
/ *保证金:4px; * /
身高:100%;
} </span>
<span style="color:rgba(0, 0, 0, 0.84)"> #previewor {
margin-left:2px;
身高:100%;
}
</ <strong>style</strong> > </span>
这将设置一个双列用户界面,一个包含editor
组件,另一个包含preview-iframe
。 这两个组件实现了前面显示的用户界面,每个组件都是Vue.js调用自定义组件的。 当应用程序运行时,Vue.js会将每个内容插入到实际的HTML中。
此处显示的<template>
中的所有内容都将位于App.vue
的<App/>
标记内,因此将显示为应用程序。 在<style>
部分,我们再次指定height
为100%
,以确保组件填充整个可用的垂直空间。
scoped
标记在此组件中排列CSS以引用此组件生成的HTML。
vue2-ace-editor
模块为我们提供了<editor>
标签。 我们通过向EditorPage.vue
添加<script>
部分来实现这一点。
<span style="color:rgba(0, 0, 0, 0.84)"> <SCRIPT>
从'./PreviewIframe.vue'导入PreviewIframe;
从'../main.js'导入{messageBus};
从'fs-extra'导入fs; </span>
<span style="color:rgba(0, 0, 0, 0.84)"> export default {
data:function(){
返回{
输入:'#hello',
isNewFile:是的,
isChangedFile:false,
文件名: ””,
layoutFileName:“”
};
},
组件: {
编辑:require('vue2-ace-editor'),
previewIframe:PreviewIframe
},
看:{
input:function(newContent,oldContent){
messageBus.newContentToRender(newContent);
this.isChangedFile = true;
}
},
计算:{
editor(){return this。$ refs.aceeditor; },
previewor(){return this。$ refs.previewor; }
},
方法: {
editorInit(编辑){
要求( '支架/ EXT / language_tools');
要求( '支架/模式/ HTML');
要求( '支架/模式/降价');
要求( '支架/主题/暮');
editor.setWrapBehavioursEnabled(真);
editor.setShowInvisibles(真);
editor.setShowFoldWidgets(真);
editor.setShowPrintMargin(真);
。editor.getSession()setUseWrapMode(真);
editor.getSession()setUseSoftTabs(真)。
messageBus.newContentToRender(this.input);
},
}
}
</ SCRIPT> </span>
在Vue.js中,使用像这样的匿名对象实例化Vue实例。 在像这样的单个文件模板中,使用默认导出描述Vue实例对象,如此处所示。
components
字段列出了此组件使用的组件。 对象中每个条目的标记将成为模板中的标记名称。 因此,“ editor
”变为“ <editor>
”标签,而“ previewIframe
”变为模板中的“ <preview-iframe>
”标签。 稍后我们将定义PreviewIframe
组件,所以请紧紧抓住。
data
字段列出了实现此组件时使用的数据。 管理数据并通知侦听器对托管数据的更改是Vue.js的核心功能之一。 此处显示的结构不是托管的实际数据,而是Vue.js在构造Vue实例时的输入。 在幕后,Vue.js设置了观察者方法和通知方法,因此如果更改了托管数据项,则会发送通知事件并更新用户界面。 有关详细信息,请参阅: https : //vuejs.org/v2/guide/components.html
input
字段包含Markdown文本。 isNewFile
标志指示是否使用New菜单栏选项创建了内容 - 我们稍后将进入菜单栏 - 而isChangedFile
指示是否已进行更改。 fileName
字段记录与此内容关联的文件名(如果有)。layoutFileName
选项记录要使用的布局模板。
editor
组件上的v-model
属性可确保将正在编辑的内容隐藏到data
列出的input
项中。
messageBus
很快就会定义,它是我们用来在组件之间发送消息的机制。Vue.js有一个组件向其父节点发送消息的机制。 如果应用程序需要将消息发送到不是父组件的组件,该怎么办? 我们将为此目的使用messageBus
。
computed
字段是用于导出值的函数的对象。 然后,组件中的其他代码将能够引用this.computedValue
来检索派生值。 有关详细信息,请参阅此处: https : //vuejs.org/v2/guide/computed.html
在这种情况下,应用程序需要引用这两个组件。 在每一个中我们添加了一个ref=” identifier ”
属性来帮助识别组件。 this.$refs
对象填充了基于每个ref=” identifier ”
的值的组件ref=” identifier ”
。 因此,这两个计算函数为我们提供了访问子组件的便捷简写。
editor
组件上的@init
属性导致vue2-ace-editor
在初始化时发出消息。 我们在这里用它来初始化Ace编辑器的外观设置。 lang
, theme
, width
和height
属性用于相同的目的。 一个非常重要的事情,因为我们将编写Markdown文本,就是设置自动换行。
watch
字段允许我们定义在数据变量更改时调用的处理函数。 在这种情况下,应用程序必须知道内容何时更改,以便可以记录此事实,以便应用程序可以重新呈现预览的HTML。
在EditorPage
组件中将实现更多功能。 但是现在,让我们通过连接预览窗格来快速获胜。
PreviewIFrame组件
要预览与Markdown文本对应的HTML,我们将使用<iframe>
和内置HTTP服务器。 原因是我们希望支持将Markdown渲染到任何布局模板中。 在<iframe>
显示呈现的HTML将提供最准确的HTML表示。
在我们第一次尝试预览HTML时,我们只是将HTML插入到<div>
组件中。但是该演示文稿受到了应用程序的CSS的影响,并且存在许多奇怪的内容,例如没有项目符号的列表。 通过使用<iframe>
我们有一个空白的板,CSS智能,正确显示渲染的HTML。
创建一个名为src/renderer/components/PreviewIframe.vue
其中包含:
<span style="color:rgba(0, 0, 0, 0.84)"> < <strong>template</strong> >
< <strong>iframe</strong> src =“”/>
</ <strong>template</strong> > </span>
<span style="color:rgba(0, 0, 0, 0.84)"> < <strong>script</strong> >
从'../main.js'导入{messageBus}; </span>
<span style="color:rgba(0, 0, 0, 0.84)"> export default {
方法: {
reload(previewSrcURL){this。$ el.src = previewSrcURL; }
},
created:function(){
messageBus。$ on('newContentToPreview',(url2preview)=> {
this.reload(url2preview);
});
}
}
</ <strong>script</strong> > </span>
<span style="color:rgba(0, 0, 0, 0.84)"> < <strong>style</strong> scoped>
iframe {身高:100%; }
</ <strong>style</strong> > </span>
该模板只是一个<iframe>
标记。 在created
函数中,我们在messageBus
的newContentToPreview
事件上设置一个监听messageBus
,然后调用reload
,它将iframe
的src
属性设置为提供的URL。
这种方法与典型的Vue.js组件实践不同。 通常会将previewSrcURL
添加到props
数组中,以将其作为可由其他代码设置的属性进行管理。
在这个应用程序中,正常的做法是行不通的。 我们的用户将在编辑器中input
,并且在每次击键时input
的值都将被更改。 我们希望这些更改传播到呈现函数,然后传播到iframe
将重新加载的此组件。
通过为src
属性分配URL,可以重新加载iframe
。 因此,我们希望为每次重新渲染Markdown时可靠地更新src
属性。 由于如果值没有变化,Vue.js不会触发对属性值的任何更新通知,我们改为使用此reload
函数。
messageBus
我们已经看过messageBus
已经多次提到了。 它是在应用程序中的组件之间发送数据的关键。
messageBus
只是一个Vue实例。 Vue实例已经提供了事件订阅和分发机制,以及方法和数据管理等其他属性。 我们可以使用它作为跨组件数据交换的手段。
返回src/renderer/main.js
并添加此代码
<span style="color:rgba(0, 0, 0, 0.84)"> export const messageBus = new Vue({
方法: {
newContentToRender(newContent){
ipcRenderer.send('newContentToRender',newContent);
},
}
}); </span>
<span style="color:rgba(0, 0, 0, 0.84)"> ipcRenderer.on('newContentToPreview',(event,url2preview)=> {
messageBus。$ emit('newContentToPreview',url2preview);
}); </span>
<span style="color:rgba(0, 0, 0, 0.84)"> ipcRenderer.on('newFile2Edit',(event)=> {
。messageBus $发射( 'newFile2Edit');
}); </span>
<span style="color:rgba(0, 0, 0, 0.84)"> ipcRenderer.on('editorDoUndo',(event)=> {
。messageBus $发射( 'editorDoUndo');
}); </span>
<span style="color:rgba(0, 0, 0, 0.84)"> ipcRenderer.on('editorDoRedo',(event)=> {
messageBus $发射( 'editorDoRedo');
}); </span>
<span style="color:rgba(0, 0, 0, 0.84)"> ipcRenderer.on('editorSelectAll',(event)=> {
。messageBus $发射( 'editorSelectAll');
}); </span>
<span style="color:rgba(0, 0, 0, 0.84)"> ipcRenderer.on('openNewFile',(event,file2open)=> {
messageBus。$ emit('openNewFile',file2open);
}); </span>
<span style="color:rgba(0, 0, 0, 0.84)"> ipcRenderer.on('saveCurrentFile',(event)=> {
。messageBus $发射( 'saveCurrentFile');
}); </span>
这里还有比我们需要的更多的事件,我们将继续使用这些事件。 像这样调用new Vue
会创建一个新的Vue实例。 messageBus
Vue实例从此模块导出,您将注意到它已导入到EditorPage
和PreviewIframe
。
我们目前看到的是newContentToRender
因为它是由EditorPage
发送的。 这会调用ipcRenderer.send
,它会将消息发送到Main进程。
我们看到了许多ipcRenderer.on
实例。 这是我们从Main进程接收消息的地方。 在每种情况下,使用messageBus.$emit
发送相应的事件在messageBus
上发送消息。
newContentToRender
Vue实例方法用于向Main进程发送相应的消息。 当Renderer进程(顾名思义)具有必须呈现的新内容时,它将被发送。
在呈现内容时,从Main进程接收另一条消息newContentToPreview
。PreviewIframe
组件侦听此事件并更新<iframe>
以查看给定的URL。
因此, Main进程必须包含用于呈现Markdown到达newContentToRender
消息的代码,使呈现的HTML在HTTP URL上可用,并使用相应的URL向Renderer进程发送newContentToPreview
消息。 正如他们所说,非常容易完成。
预览服务器
如前所述,我们需要一个简单的HTTP服务器来提供呈现的HTML。
你可能会挠头,想知道发生了什么。 我们有一个<iframe>
通过HTTP连接请求呈现的HTML到同一进程中的HTTP服务器。 我们去过疯了吗?
要求是使用HTTP向<iframe>
提供HTML。 因此,我们需要一个HTTP服务器,它也可能在Main进程中运行。 在同一进程内管理HTTP服务器要比通过旋转和管理包含HTTP服务器的子进程容易得多。 Node.js可以轻松构建这种简单的HTTP服务器。
考虑到这一点,创建一个名为src/main/preview-server.js
其中包含:
<span style="color:rgba(0, 0, 0, 0.84)"> 从'http'导入http;
从'url'导入网址; </span>
<span style="color:rgba(0, 0, 0, 0.84)"> var服务器;
var内容; </span>
<span style="color:rgba(0, 0, 0, 0.84)"> export function createServer(){
if(server)抛出新错误(“服务器已启动”);
server = http.createServer(requestHandler);
server.listen(0,“127.0.0.1”);
} </span>
<span style="color:rgba(0, 0, 0, 0.84)"> export function newContent(text){
content = text;
return genurl('content');
} </span>
<span style="color:rgba(0, 0, 0, 0.84)"> export function currentContent(){
返回内容;
} </span>
<span style="color:rgba(0, 0, 0, 0.84)"> function genurl(pathname){
const url2preview = url.format({
协议:'http',
hostname:server.address()。address,
port:server.address()。port,
pathname:pathname
});
return url2preview;
} </span>
<span style="color:rgba(0, 0, 0, 0.84)"> function requestHandler(req,res){
尝试{
res.writeHead(200,{
'Content-Type':'text / html',
'Content-Length':content.length
});
res.end(内容);
} catch(err){
res.writeHead(500,{
'Content-Type':'text / plain'
});
res.end(err.stack);
}
} </span>
通过调用createServer
来初始化模块, createServer
设置HTTPServer
对象。我们没有提供端口号,而是为我们分配了一个端口号,因此我们不必担心端口号与计算机上的任何现有端口冲突。 因此, genurl
函数查询server
对象以找出IP地址和端口号。 另一个细节是我们强制它只监听IP地址127.0.0.1
,以尽量减少不法分子潜入应用程序的机会。 可以采取另一步骤并生成添加到URL的随机令牌,并拒绝缺少有效令牌的连接请求。
每当呈现的内容可用时,我们将使用newContent
函数通知我们。 内容存储在全局变量中。 此函数返回可用于获取内容的URL。 请注意,URL是硬编码的。
currentContent
函数执行名称建议,并返回当前呈现的内容。 我们将使用它来实现“ 导出到HTML”功能。
使用什么URL并不重要,因为requestHandler
函数对任何请求的URL进行相同的响应,即呈现的内容。
将Markdown呈现为HTML
现在我们有了预览服务器,让我们来看看如何将Markdown呈现为HTML。
Node.js有许多Markdown渲染器库。 在这个项目中,我们将使用Markdown-it,因为它非常受欢迎,它支持CommonMark。 它还支持一长串插件,增加额外的功能,如脚注。 开箱即用它支持类似Github的表。 请参阅https://www.npmjs.com/package/markdown-it
创建一个名为src/main/renderer.js
的文件,其中包含:
<span style="color:rgba(0, 0, 0, 0.84)"> 从'markdown-it'导入mdit;
从'ejs'导入ejs; </span>
<span style="color:rgba(0, 0, 0, 0.84)"> const mditConfig = {
html:true,xhtmlOut:true,
break:false,linkify:true,
印刷师:假的,
highlight:function(/ * str ,, lang * /){return''; }
};
const md = mdit(mditConfig); </span>
<span style="color:rgba(0, 0, 0, 0.84)"> const layouts = []; </span>
<span style="color:rgba(0, 0, 0, 0.84)"> export function renderContent(content,layoutFile){
const text = md.render(content);
const layout = layouts [layoutFile];
const rendered = ejs.render(layout,{
标题:'页面标题',
内容:文字
});
返回呈现;
} </span>
<span style="color:rgba(0, 0, 0, 0.84)"> 布局['layout1.html'] =`
<HTML>
<HEAD>
<title> <%= title%> </ title>
<风格>
H1,h2,h3,h4 {颜色:红色; }
前{
背景:rgb(59,58,58);
白颜色;
}
</样式>
</ HEAD>
<body> <% - content%> </ body>
</ HTML>`; </span>
Markdown-它需要一个具有许多选项的配置对象。 这就是我们在模块顶部所做的事情。
renderContent
函数不仅注意将Markdown呈现为HTML,而且还使用模板呈现该HTML。 该概念可能支持许多模板,这些模板将作为文件存储在文件系统中。 现在我们可以使用这个,并将其保存在内存中。
主要过程
现在我们可以通过修改Main进程来设置消息接收,并调度到渲染器和HTTP服务器模块来实现这一切。 在src/main/main.js
开始进行更改
<span style="color:rgba(0, 0, 0, 0.84)"> import {
app,BrowserWindow,ipcMain,对话框
来自'电子';
从'./renderer.js'导入{renderContent};
从'./preview-server.js'导入{createServer,newContent}; </span>
这带来了Electron和我们刚刚实现的两个模块的必要功能。
<span style="color:rgba(0, 0, 0, 0.84)"> function createWindow(){
mainWindow = new BrowserWindow({
高度:563,useContentSize:true,
width:1000,webPreferences:{backgroundThrottling:false}
});
mainWindow.loadURL(winURL);
if(process.env.NODE_ENV ==='development'){
mainWindow.webContents.openDevTools();
}
createServer(); </span>
<span style="color:rgba(0, 0, 0, 0.84)"> mainWindow.on('closed',()=> {
mainWindow = null
});
} </span>
createWindow
函数就是我们顾名思义创建主窗口的地方。 当Electron过程启动时, app.on('ready')
和app.on('activate')
处理程序会自动调用此函数。
BrowserWindow
对象是在Electron中包装Chromium窗口的对象。 它是用于所有意图和目的的Web浏览器,我们将用它来托管我们的应用程序代码。
接下来的两行表明这是一个Web浏览器。 我们首先让BrowserWindow
访问一个URL。 该URL的计算取决于应用程序是否在开发模式下运行。 在开发模式中,它使用与Webpack服务相对应的URL,否则它引用已部署应用程序中的位置。
其次,我们会自动打开Chrome开发者工具。 请记住,这是Electron给我们的一个巨大优势,因为我们可以访问世界级的开发人员工具台,用它来检查应用程序的HTML + CSS + JavaScript代码。 在开发模式下运行时禁用此功能。
最后,我们调用createServer
来初始化预览服务器。
最后,在底部添加这两个功能。
<span style="color:rgba(0, 0, 0, 0.84)"> ipcMain.on('newContentToRender',function(event,content){
const rendered = renderContent(content,'layout1.html');
const previewURL = newContent(render);
mainWindow.webContents.send('newContentToPreview',previewURL);
}); </span>
<span style="color:rgba(0, 0, 0, 0.84)"> process.on('unhandledRejection',(reason,p)=> {
console.error(`未处理的拒绝:$ {util.inspect(p)}原因:$ {reason}`);
}); </span>
newContentToRender
事件处理程序是我们将Markdown呈现为HTML并在PreviewIframe
显示它的PreviewIframe
。 我们调用renderContent
来呈现文本,然后通过调用newContent
函数通知预览服务器,最后将预览URL发送到Renderer进程。 如果您回头一下,您会记得newContentToPreview
消息由PreviewIframe
组件处理,并导致<iframe>
重新加载给定的URL。
unhandledRejection
处理程序很重要,在Node.js的更高版本中将成为必需。此事件是在拒绝状态下为Promises发出的,但没有代码捕获拒绝Promise。换句话说, unhandledRejection
是未被捕获的错误。 显然很糟糕的是没有捕获到你的错误,并且Node.js计划在将来的版本中通过使应用程序退出此事件来强制执行此操作。 因此,我们都必须习惯于添加此处理程序,以便我们可以警告我们未被捕获的错误。
顺便说一下,代码发出console.error
每个地方都应该在应用程序中添加一条可见的警告消息。
启动应用程序
我们现在有足够的代码来运行编辑器应用程序。 它将无法打开文件或保存文件,但我们将能够使用编辑器,查看预览输出,并使用开发人员工具。
首先运行这个:
<span style="color:rgba(0, 0, 0, 0.84)"> $ npm install buefy ejs markdown-it - save
$ npm安装 </span>
我们添加了几个包,因此我们需要将它们添加到package.json中,然后重新安装。
<span style="color:rgba(0, 0, 0, 0.84)"> $ npm run dev </span>
这将以开发人员模式运行应用程序。 如果你检查生成的package.json,你会看到许多其他可以运行的脚本,我们稍后会介绍它们。 在任何情况下,在进行一些编辑后,我们最终会得到一个如下所示的窗口:
这是一个相当大的进步,但我们有一些明显缺少功能。 对于初学者:
- 系统菜单是Electron提供的默认设置
- 可以通过在顶部添加工具栏按钮来改进应用程序。
- 它需要保存呈现的HTML。
- 生成可安装的应用程序。
让我们在后续章节中处理这些任务。
应用菜单
Electron提供了一种非常简单的机制来指定系统菜单,甚至可以正确地与不同的Windows和Mac菜单范例正确集成。 一个创建一个menuTemplate
对象,交给Electron,然后安装菜单项。
在src/main
创建一个名为mainMenu.js
的新文件,其中包含:
<span style="color:rgba(0, 0, 0, 0.84)"> 从'电子'导入{app,Menu,dialog};
从'./preview-server.js'导入{currentContent};
从'fs-extra'导入fs; </span>
<span style="color:rgba(0, 0, 0, 0.84)"> export function mainMenu(mainWindow){
const menuTemplate = [{
label:'文件',
子菜单:[
{
标签:'新',
加速器:
process.platform ==='darwin'? 'Command + N':'Ctrl + N',
click:()=> {mainWindow.webContents.send('newFile2Edit'); }
},
{
标签:'打开',
加速器:
process.platform ==='darwin'? 'Command + O':'Ctrl + O',
click:()=> {mainWindow.webContents.send('openNewFile'); }
},
{
标签:'保存',
加速器:
process.platform ==='darwin'? 'Command + S':'Ctrl + S',
click:()=> {mainWindow.webContents.send('saveCurrentFile'); }
},
{
label:'导出为HTML',
click:async()=> {
let filename =等待新的Promise((resolve,reject)=> {
dialog.showSaveDialog({
标题:“导出为HTML”
},filename => {
的console.log(
`导出到HTML GOT SAVE TO $ {filename}`);
if(filename){
解决(文件名);
} else {
解析(未定义);
}
});
});
if(filename){
等待fs.writeFile(文件名,
currentContent(),'utf8');
}
}
},
{
标签:'退出',
加速器:
process.platform ==='darwin'? 'Command + Q':'Ctrl + Q',
click:()=> {app.quit(); }
}
]
},
{
标签:'编辑',
子菜单:[{
标签:'撤消',
加速器:process.platform ==='darwin'
? 'Command + Z':'Ctrl + Z',
click:()=> {
mainWindow.webContents.send( 'editorDoUndo'); }
},
{
标签:'重做',
加速器:process.platform ==='darwin'
? 'Command + Shift + Z':'Ctrl + Shift + Z',
click:()=> {
mainWindow.webContents.send( 'editorDoRedo'); }
},
{type:'separator'},
{role:'cut'},
{role:'copy'},
{role:'paste'},
{role:'pasteandmatchstyle'},
{role:'delete'},
{
label:'全选',
加速器:process.platform ==='darwin'
? 'Command + A':'Ctrl + A',
click:()=> {
mainWindow.webContents.send( 'editorSelectAll'); }
}
]
},
{
标签:'查看',
子菜单:[
{role:'reload'},
{role:'forcereload'},
{role:'toggledevtools'},
{type:'separator'},
{role:'resetzoom'},
{role:'zoomin'},
{role:'zoomout'},
{type:'separator'},
{role:'togglefullscreen'}
]
},
{
角色:'窗口',
子菜单:[
{role:'minimize'},
{role:'close'}
]
}];
if(process.platform ==='darwin'){
menuTemplate.unshift({
label:app.getName(),
子菜单:[
{role:'about'},
{type:'separator'},
{role:'services',子菜单:[]},
{type:'separator'},
{role:'hide'},
{role:'hideothers'},
{role:'取消隐藏'},
{type:'separator'},
{
角色:'退出',
加速器:process.platform ==='darwin'
? 'Command + Q':'Ctrl + Q'
}
]
});
} </span>
<span style="color:rgba(0, 0, 0, 0.84)"> const mainMenu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(MAINMENU);
}; </span>
这些是相当典型的菜单选择。 在许多情况下,菜单项处理程序将事件从Main进程发送到Renderer进程。 这需要在该过程中实现匹配的事件处理程序。 在其他情况下,菜单项使用内置处理程序。
导出到HTML功能是个例外。 此功能非常简单,可以像这样实现内联。 我们只需使用Electron Save As对话框询问用户文件名(请参阅https://electronjs.org/docs/api/dialog )。 如果用户点击CANCEL,那么我们将undefined
为filename
,否则我们将获得一个文件名,在这种情况下,我们只需将currentContent
写入指定文件。
在src/main/index.js
将此行添加到createWindow
函数:
<span style="color:rgba(0, 0, 0, 0.84)"> MAINMENU(主窗口); </span>
这样,菜单会在应用程序运行时进行设置。
要处理所有这些事件,请在src/renderer/components/EditorPage.vue
添加此created
函数:
<span style="color:rgba(0, 0, 0, 0.84)"> created:function(){
messageBus。$ on('newFile2Edit',(targetWindow)=> {
this.newFile2Edit(targetWindow);
});
messageBus。$ on('editorDoUndo',()=> {
this.editor.editor.undo();
});
messageBus。$ on('editorDoRedo',()=> {
this.editor.editor.redo();
});
messageBus。$ on('editorSelectAll',()=> {
this.editor.editor.selectAll();
});
messageBus。$ on('openNewFile',async(file2open)=> {
试试{this.openNewFile(); } catch(err){
console.error(`openNewFile ERROR $ {file2open} $ {err.stack}`);
}
});
messageBus。$ on('saveCurrentFile',()=> {
试试{this.saveCurrentFile(); } catch(err){
console.error(
`saveCurrentFile ERROR $ {file2open} $ {err.stack}`);
}
});
}, </span>
对于每个我们收到消息,将其路由到适当的位置,例如我们现在必须添加到EditorPage.vue
一些方法。 在methods对象中,添加以下函数:
<span style="color:rgba(0, 0, 0, 0.84)"> editorChanged(输入){this.isChangedFile = true; },
askSaveFile(file2save){
返回新的Promise((resolve,reject)=> {
这一点。$ dialog.confirm({
标题:“保存文件?”,
消息:`$ {file2save}`,
cancelText:'不',
confirmText:'是',
onCancel :()=> {resolve(“cancel”); },
onConfirm :()=> {resolve(“confirm”); }
})
});
},
async saveContentToFile(file2save){
return await fs.writeFile(file2save,this.input,'utf8');
},
saveAsGetFileName(){
const remote = this。$ electron.remote;
const dialog = remote.dialog;
返回新的Promise((resolve,reject)=> {
尝试{
dialog.showSaveDialog({
标题:“保存”
},filename => {resolve(filename); });
} catch(err){reject(err); }
});
},
async openNewFile(){
if(this.isNewFile && this.isChangedFile){
让doit = await this.askSaveFile('UNTITLED');
if(doit ===“confirm”){
let fileName = await this.saveAsGetFileName();
try {await this.saveContentToFile(fileName); } catch(e){
console.error(`openNewFile saveContentToFile FAIL,因为$ {fileName} $ {e.stack}`);
}
}
} else if(this.isChangedFile){
let doit = await this.askSaveFile(this.fileName);
}
让file2open =等待新的Promise((resolve,reject)=> {
const remote = this。$ electron.remote;
const dialog = remote.dialog;
dialog.showOpenDialog({
属性:['openFile'],
标题:“打开文档”,
过滤器:[{
名称:“Markdown文件”,
扩展名:[“md”]
}]
},
filePaths => {
if(filePaths){
决心(文件路径[0]);
} else resolve(undefined);
});
});
if(!file2open)返回;
等待新的Promise((resolve,reject)=> {
fs.readFile(file2open,'utf8',(err,text)=> {
if(err)拒绝(错误);
其他{
this.isNewFile = false;
this.isChangedFile = false;
this.fileName = file2open;
this.input = text;
解决();
}
});
});
},
async saveCurrentFile(){
让p;
let fileName;
if(this.isNewFile && this.isChangedFile){
fileName = await this.saveAsGetFileName();
if(!fileName)返回;
} else if(this.isChangedFile){
fileName = this.fileName;
否则返回;
try {await this.saveContentToFile(fileName); } catch(e){
console.error(`openNewFile saveContentToFile FAIL,因为$ {fileName} $ {e.stack}`);
}
this.isNewFile = false;
this.isChangedFile = false;
this.fileName = fileName;
},
async newFile2Edit(){
让p;
if(this.isNewFile && this.isChangedFile){
让doit = await this.askSaveFile('UNTITLED');
if(doit ===“confirm”){
let fileName = await this.saveAsGetFileName();
try {await this.saveContentToFile(fileName); } catch(e){
console.error(`openNewFile saveContentToFile FAIL,因为$ {fileName} $ {e.stack}`);
}
}
} else if(this.isChangedFile){
let doit = await this.askSaveFile(this.fileName);
if(doit ===“confirm”){
try {await this.saveContentToFile(fileName); } catch(e){
console.error(`openNewFile saveContentToFile FAIL,因为$ {fileName} $ {e.stack}`);
}
}
}
this.isNewFile = true;
this.isChangedFile = false;
this.fileName = undefined;
this.layoutFileName = undefined;
this.input =“#hello”;
} </span>
在editorChanged
我们只是跟踪内容是否已更改。 此处维护的标志将在其余代码中使用。
使用askSaveFile
每当我们需要询问是否保存当前文件时,我们都会使用一个便利函数。 这使用Buefy对话框来提问。 另一个便利函数saveContentToFile
只是将内容(在输入对象中)保存到指定文件中。 而另一个saveAsGetFileName
使用Electron File Save对话框来获取用于保存内容的文件名。
在这两个对话框之间,我们看到了两种创建对话框的方法。 在一种情况下,我们有Buefy对话框,而在另一种情况下,我们使用Electron对话框。 电子时,Buefy不提供文件打开或文件保存对话框。 通常,Electron对话框是从Main进程启动的,但是我们在这里从Renderer进程启动它们。 Electron支持remote
对象,该对象支持从Renderer进程访问主进程资源,如对话框。
在openNewFile
我们处理打开文件。 我们必须考虑当前编辑缓冲区是否是“新”文件,意味着它是否已经与文件关联,缓冲区是否已被修改。 每个考虑都会导致出现一组不同的对话框。 在第一阶段,代码确定是否保存当前编辑缓冲区,如果是,则保存它的文件名。 在第二阶段,它询问用户要打开哪个文件。 如果用户确实选择了文件,则会读取该文件并将其分配给数据对象。
通过为数据对象赋值,可以触发一系列副作用,例如使文本出现在编辑器中,然后将预览生成到<iframe>
。
在saveCurrentFile
我们只关心保存文件。 到目前为止,我们只支持Save ,而不是Save As 。 因此,我们唯一一次询问要保存的文件名是在编辑器尚未与文件关联时。 否则,程序只是将编辑器缓冲区保存到关联文件中。
在newFile2Edit
我们只关心打开一个新文件。 需要考虑的是现有的编辑器缓冲区是否已更改,以及如何将缓冲区保存到文件中。 如果缓冲区未与文件关联,则必须使用“ 另存为”对话框来请求文件名。
工具栏,状态栏
这种应用程序通常具有带便利按钮的工具栏和显示信息的状态栏。在这种情况下,我们可能需要一些Markdown快捷方式,我们当然希望显示当前文件名和编辑状态。
对于工具栏按钮,我们将使用之前加载的Material Design图标包。要使用它,请将以下代码添加到src/renderer/main.js
:
<span style="color:rgba(0, 0, 0, 0.84)">导入“vue-material-design-icons / styles.css”
从“vue-material-design-icons / file-plus.vue”
导入FilePlus 从“vue-material-design-icons / content-save.vue”导入ContentSave
从“vue-material-design-icons / folder-open.vue”
导入FolderOpen 从“vue-material-design-icons / content-cut.vue”
导入ContentCut从“vue-material-design-icons / format- ” 导入FormatBold bold.vue“
import FormatItalic from”vue-material-design-icons / format-italic.vue“</span>
<span style="color:rgba(0, 0, 0, 0.84)">Vue.component(“content-save”,ContentSave);
Vue.component(“file-plus”,FilePlus);
Vue.component(“folder-open”,FolderOpen);
Vue.component(“content-cut”,ContentCut);
Vue.component(“format-bold”,FormatBold);
Vue.component(“format-italic”,FormatItalic);</span>
该vue-material-design-icons
包为每个图标实现一个Vue组件。图标名称如https://materialdesignicons.com/所示。对于您要使用的每个组件,请导入它,然后Vue.component
使用导入的对象和所需的标记名称进行调用。对于每个创建的全局Vue组件,可以在应用程序的任何位置使用。
在src/renderer/components/EditorPage.vue
更改<template>
时添加一行按钮:
<span style="color:rgba(0, 0, 0, 0.84)">< <strong>div</strong> id =“wrapper”>
< <strong>b-tooltip</strong> label =“New file”position =“is-right”>
< <strong>button</strong> class =“button”@ click =“newFile2Edit”>
< <strong>file-plus</strong> > </ <strong>file-plus</strong> >
</ <strong>button</strong> >
</ <strong>b-tooltip</strong> >
< <strong>b-tooltip</strong> label =“保存文件”position =“is-right”>
< <strong>button</strong> class =“button”@ click =“saveCurrentFile”>
< <strong>content-save</strong> > </ <strong>content-save</strong> >
</ <strong>button</strong> >
</ <strong>b-tooltip</strong> >
< <strong>b-tooltip</strong> label =“打开文件”position =“is-bottom”>
< <strong>button</strong> class =“button”@ click =“openNewFile”>
< <strong>folder-open</strong> > </ <strong>folder-open</strong> >
</ <strong>button</strong> >
</ <strong>b-tooltip</strong> >
< <strong>b-tooltip</strong> label =“Cut”position =“is-bottom”>
< <strong>button</strong> class =“button”@ click =“editorContentCut”>
< <strong>content-cut</strong> > </ <strong>content-cut</strong> >
</ <strong>button</strong> >
</ <strong>b-tooltip</strong> >
< <strong>b-tooltip</strong> label =“插入粗体”position =“is-bottom”>
< <strong>button</strong> class =“button”@ click =“editorFormatBold”>
< <strong>format-bold</strong> > </ <strong>format-bold</strong> >
</ <strong>button</strong> >
</ <strong>b-tooltip</strong> >
< <strong>b-tooltip</strong> label =“Insert italic”position =“is-bottom”>
< <strong>button</strong> class =“button”@ click =“editorFormatItalic”>
< <strong>format-italic</strong> > </ <strong>format-italic</strong> >
</ <strong>button</strong> >
</ <strong>b-tooltip</strong> >
..
</ <strong>div</strong> > </span>
模板的其余部分将保持不变,我们将此行按钮添加到页面顶部。每个都有一个on-click事件处理程序,在大多数情况下,处理程序调用已经存在的函数。我们确实有一些新的处理函数要实现。
我们还添加了工具提示,使应用程序更友好一些。因为每个按钮只是使用图标,所以用户确实需要一些单词来与图标图像一起使用。
三个新的事件处理函数是:
<span style="color:rgba(0, 0, 0, 0.84)">editorContentCut(){
let selected = this.editor.editor.getSelection();
if(!selected.isEmpty()){
let selectedRange = this.editor.editor.getSelectionRange();
this.editor.editor.getSession()。
getDocument()。replace(selectedRange,'');
}
这个。$ nextTick(()=> {this.editor.editor.focus();});
},
editorFormatBold(){
let selected = this.editor.editor.getSelection();
if(!selected.isEmpty()){
let selectedRange = this.editor.editor.getSelectionRange();
let selectedText = this.editor.editor.getSession()。
getDocument()。getTextRange(selectedRange);
this.editor.editor.getSession()。getDocument().
replace(
selectedRange,`** $ {selectedText} **`);
} else {
this.editor.editor.insert( '** ** BOLD');
}
这个。$ nextTick(()=> {this.editor.editor.focus();});
},
editorFormatItalic(){
let selected = this.editor.editor.getSelection();
if(!selected.isEmpty()){
let selectedRange = this.editor.editor.getSelectionRange();
let selectedText = this.editor.editor.getSession()。
getDocument()。getTextRange(selectedRange);
this.editor.editor.getSession()。getDocument().
replace(
selectedRange,`_ $ {selectedText} _`);
} else {
this.editor.editor.insert( '_ Italic_');
}
这个。$ nextTick(()=> {this.editor.editor.focus();});
}, </span>
虽然我们并没有完全实现一套完整的工具栏按钮,但我们在这种应用程序中展示了一些有用的模式。通过这三个函数,我们将展示如何访问和修改Ace编辑器组件中的内容。
另一个细节是调用该focus
方法。有人观察到你可能在编辑器中输入文本,然后用鼠标单击一个按钮,然后你想继续打字。由于focus
按钮位于按钮上,键盘事件会触发更多按钮按下。通过更改焦点,焦点停留在编辑器中而不是转移到按钮。
接下来,我们需要调整窗口的布局以适应工具栏。
<span style="color:rgba(0, 0, 0, 0.84)"><style scoped>
#wrapper {
height:100%;
最小高度:100%;
}
.button-bar {
margin-bottom:0px!important;
身高:40px;
} </span>
<span style="color:rgba(0, 0, 0, 0.84)">.button-bar按钮,
.button-bar a.navbar-item {
padding:0px;
} </span>
<span style="color:rgba(0, 0, 0, 0.84)"> #editor {
位置:绝对;
上:40px;
底部:0px;
左:0px;
右:0px;
} </span>
<span style="color:rgba(0, 0, 0, 0.84)">#aceeditor {
height:100%;
最小高度:100%;
} </span>
<span style="color:rgba(0, 0, 0, 0.84)">#previewor {
margin-left:2px;
身高:100%;
最小高度:100%;
}
</样式> </span>
我们在工具栏中引入了一些复杂性。以前的布局是通过设置height:100%;
所有内容来完成的。但是在工具栏就位的情况下,编辑器部分位于窗口底部以下,并且滚动行为不太理想。
在经过大量研究寻找更现代的方法来完成这种布局之后,使用了这种旧的静态定位待机。<nav>
尝试了Bulma支持的几种标记变体。相反,将工具栏静态定位在窗口顶部,具有固定高度,然后静态定位编辑器区域,使其顶部与工具栏的固定高度匹配。
到目前为止,应用程序已经取得了一些进展。我们现在有一个可以构建的功能工具栏。显然,我们可以轻松地向工具栏添加更多按钮,但我们在底部实现状态栏具有更高的优先级。
返回EditorPage.vue
,将以下内容添加到模板的底部
<span style="color:rgba(0, 0, 0, 0.84)">< <strong>section</strong> class =“status-bar columns”>
< <strong>span</strong> class =“status-item column tag is-info”> {{fileName}} </ <strong>span</strong> >
< <strong>span</strong> class =“status-item column tag is-info”>
{{input.length}}个字节</ <strong>span</strong> >
< <strong>span</strong> class =“status-item column tag is-info”>
{{isChangedFile? “已更改”:“”}} </ <strong>span</strong> >
< <strong>span</strong> class =“status-item列标记is-info”>
{{isNewFile? “新”:“”}} </ <strong>span</strong> >
</ <strong>section</strong> ></span>
我们所做的就是使某些数据值显示出来。当值发生变化时,Vue.js将自动更新显示,无需再编写任何代码。我们使用Bulma标签元素进行有用的着色。
然后将该<style>
部分更改为以下内容:
<span style="color:rgba(0, 0, 0, 0.84)"><style scoped>
#wrapper {..}
.button-bar {..}
.button-bar button,
.button-bar a.navbar-item {..}
#editor {
位置:绝对;
上:40px;
底部:30px;
左:0px;
右:0px;
保证金:0px;
}
#aceeditor {..}
#previewor {..}
.status-bar {
位置:绝对;
身高:30px;
右:0px;
左:0px;
底部:0px;
保证金:0px;
}
.status-bar .status-item {
vertical-align:middle;
}
</样式> </span>
继续使用相同的静态布局方法,我们status-bar
将窗口的高度设置为30像素。这意味着编辑器的底部必须高出窗口底部30个像素。
我们现在在屏幕底部有一个状态栏。
生成可安装的应用程序
使用Electron构建的所有商业应用程序都可以作为整齐打包的文件提供,其安装方式与任何其他应用程序一样。有多少人使用Postman知道或关心它是使用Electron构建的?安装任何这些应用程序都不需要任何异常 - 只需下载安装程序,然后像在给定平台上那样运行它。
该electron-vue
框架使我们可以轻松创建此类安装包。
查看scripts
部分,package.json
您将看到几个名为“ build
”的部分。因为我们一开始就配置了Electron-Vue使用electron-builder
。
要构建Mac包,请运行:
<span style="color:rgba(0, 0, 0, 0.84)"> $ npm run build </span>
<span style="color:rgba(0, 0, 0, 0.84)">> electron-vue-buefy-editor@0.0.0 build / Volumes / Extra / sourcerer / 004-electron / electron-vue-buefy-editor
> node .electron-vue / build.js && electron-builder
.. </span>
<span style="color:rgba(0, 0, 0, 0.84)">✔构建主要流程
✔构建渲染器流程</span>
<span style="color:rgba(0, 0, 0, 0.84)"> .. </span>
<span style="color:rgba(0, 0, 0, 0.84)">•构建目标= macOS zip arch = x64 file = build / electron-vue-buefy-editor-0.0.0-mac.zip
•building target = DMG arch = x64 file = build / electron-vue-buefy-editor-0.0。 0.dmg
•构建块映射blockMapFile = build / electron-vue-buefy-editor-0.0.0.dmg.blockmap</span>
<span style="color:rgba(0, 0, 0, 0.84)"> .. </span>
<span style="color:rgba(0, 0, 0, 0.84)">$ ls -l build /
total 196760
-rw-r - r - 1 david admin 412 Jul 13 14:15 electron-builder.yaml
-rw-r - r - 1 david admin 49188099 7月13日14:17 electron-vue-buefy -editor-0.0.0-mac.zip
-rw-r - r - @ 1 david admin 51490606 7月13日14:16 electron-vue-buefy-editor-0.0.0.dmg
-rw-r -r-1 david admin 55383 Jul 13 14:16 electron-vue-buefy-editor-0.0.0.dmg.blockmap
drwxr-xr-x 5 david admin 170 Jul 13 13:55 icons
drwxr-xr-x 3 david admin 102 Jul 13 14:15苹果电脑</span>
由于它创建了一个DMG文件,让我们来看看。双击DMG文件打开一个熟悉的Finder窗口,支持安装应用程序:
我们可以直接在这里双击应用程序,然后启动应用程序。
Electron支持跨平台应用程序。我们刚刚证明我们可以在MacOSX上构建应用程序,但是Windows呢?
将相同的源代码添加到Windows计算机。安装Node.js,按照安装说明操作,按照打包说明操作,最后得到一个EXE文件,它是一个应用程序安装程序。双击该EXE,然后安装该应用程序。它将显示在“开始”菜单中,您可以启动该应用程序。您可以进入Windows控制面板,在已安装的应用程序列表中找到该应用程序。该应用程序看起来与我们之前显示的相同 - 除了菜单栏位于应用程序窗口的顶部,就像Windows上的标准一样。 Electron会自动处理许多细节。
结论
我们在本教程中已经走了很长的路,并开发了一个有用的示例应用程序。在此过程中,我们已经看到在Electron中组装Vue.js应用程序是多么容易,可以在多个桌面计算机环境中提供。
显然,这些部件可以放在一起,以满足您的任何应用需求。这取决于您,Electron可以相对轻松地在多个平台上提供高保真应用。如果不出意外,Atom,Visual Studio Code,Postman等基于电子的应用程序的成功证明了可能性。
https://blog.sourcerer.io/creating-a-markdown-editor-previewer-in-electron-and-vue-js-32a084e7b8fe