使用 Electron 开发一款理财计算器

BonnieReard 8年前
   <p><a href="/misc/goto?guid=4958875078695602791" rel="nofollow,noindex">Electron</a> 是一个跨平台应用框架,原本为 <a href="/misc/goto?guid=4958832861167207492" rel="nofollow,noindex">Atom</a> 设计,后来单独分离了出来。它提供了一些与原生系统交互的 API ,可以方便快速使用 HTML、JavaScript、CSS 等 Web 技术开发跨平台应用。</p>    <p>最近基于 Electron 开发了一个简单的 <a href="/misc/goto?guid=4959674950887706545" rel="nofollow,noindex">理财计算器</a> 。发布了 1.0 版本之后,总结一下这段时间的开发经历。哦对了,价格是随便定的,主要是熟悉下流程,大家千万别买。</p>    <p>这篇文章只包含个人开发小结,不包括各种框架工具的基础使用,如有需要请自行 Google 。</p>    <h2>过程</h2>    <p>理财计算器的功能很简单,目前只做了股票的保本卖出价的计算。</p>    <p><img src="https://simg.open-open.com/show/f1866c691c7be780830634cda57cf015.jpg"></p>    <p>输入买入价、买入数量等信息,算上各种手续费之后,输出保本卖出的价格。</p>    <p>看起来很简单的功能,大概却做了5天,主要在开发环境配置上卡了挺久。</p>    <h3>基础项目</h3>    <p>一开始需要做的是搭建整个项目的基础框架,主要包含以下几个方面:</p>    <ul>     <li><a href="/misc/goto?guid=4958875078695602791" rel="nofollow,noindex">electron</a> :跨平台框架,主要是封装 Web 应用,并且打包发布。</li>     <li><a href="/misc/goto?guid=4958870939634769772" rel="nofollow,noindex">webpack</a> :模块加载打包工具,可以通过各种 loader 加载不同资源。</li>     <li><a href="/misc/goto?guid=4958870940891400873" rel="nofollow,noindex">eslint</a> :JS 代码静态检测工具,可以方便的统一代码的风格。</li>     <li><a href="/misc/goto?guid=4958871204393151840" rel="nofollow,noindex">babel</a> :可以将 ES6 代码转为 ES5 代码的语法编译器。</li>     <li><a href="/misc/goto?guid=4958857378973396767" rel="nofollow,noindex">vue</a> :前端框架,数据驱动的组件化开发,配上 <a href="/misc/goto?guid=4959674951096562054" rel="nofollow,noindex">vue-loader</a> 之后十分好用。</li>    </ul>    <p>上面这一套东西要搞一下还是比较费时间的,好在网上有各种现成的模板,货比三家之后最后选用了 <a href="/misc/goto?guid=4959674951187836126" rel="nofollow,noindex">electron-boilerplate-vue</a> ,搭建好后文件目录如下:</p>    <pre>  <code class="language-javascript">.  ├── .babelrc  ├── .editorconfig  ├── .eslintrc.js  ├── .gitignore  ├── README.md  ├── app  │   ├── ...  │   ├── main.html  │   ├── main.js  │   └── package.json  ├── build  │   ├── ...  │   ├── webpack.base.conf.js  │   ├── webpack.dev-background.conf.js  │   ├── webpack.dev-server.conf.js  │   └── webpack.pro-build.conf.js  ├── config.js  ├── package.json  └── test  </code></pre>    <h3>前端页面</h3>    <p>接下来就是前端页面部分。一直想试试 Material Design 风格,刚好这次是个不错的机会,于是就开始找找有没有 MD 方面的前端库。</p>    <p>先是想看看有没有类似于 <a href="/misc/goto?guid=4959674951260994829" rel="nofollow,noindex">Ant Design</a> 这样的成套 vue 组件库,于是找到了 <a href="/misc/goto?guid=4959007488104018325" rel="nofollow,noindex">material-ui</a> ,可惜是 React 版本的。找找了 vue 版本的似乎只有 <a href="/misc/goto?guid=4959674951379196224" rel="nofollow,noindex">vue-mdl</a> 和 <a href="/misc/goto?guid=4959674951462292091" rel="nofollow,noindex">vue-material</a> 这两个不太成气候的库,仔细想想本身它们都依赖于其他前端库和 vue ,万一更新不及时岂不是很坑爹。于是弃了这个想法。</p>    <p>后来就是去找类似于 <a href="/misc/goto?guid=4958542168966082381" rel="nofollow,noindex">semantic-ui</a> 和 <a href="/misc/goto?guid=4958338175196780124" rel="nofollow,noindex">Foundation</a> 这样纯粹的前端库,找到 <a href="/misc/goto?guid=4958862032806464243" rel="nofollow,noindex">materializecss</a> 这个库,页面简洁大方,破儿费(Perfect)啊!</p>    <p>然后,就是为期两天的填坑之旅。</p>    <p>是的,一共5天的开发时间,调教这个前端库花了2天,而且坑爹的是,最后还没用上。主要问题在于它的 JS 代码是基于 jQuery 的,而很多插件又是在 $(document).ready 里加载的,对 SPA 非常不友好,因为 document ready 的时候我的 dom 们还没 ready 。再加上我对 Webpack 又不是很熟悉,导致点击事件不响应,不知道是 webpack 打包的问题还是 vue-loader 加载顺序的问题还是 babel-loader 编译的问题还是它自己库的问题还是 jquery 引入的问题。</p>    <p>总之就是,一套系统过于庞大,虽然脚手架让我避开了大量的体力劳动,但是同时也引入了很多我不熟悉的未知因素。于是就刚好花两天时间,仔细学习了项目中用到的各种技术和工具。熟悉了之后才发现我以前多虑了,问题肯定出在源码上。然后就找到了问题的原因。</p>    <p>最后决定放弃 materializecss 这个库,然后去试了试 Google 家的 <a href="/misc/goto?guid=4959638705612557178" rel="nofollow,noindex">Material Design Lite</a> 。</p>    <p>万万没想到,5分钟解决战斗。</p>    <p>引入 CSS 的方法和 materializecss 类似,都是在 SCSS 里 import 它的原文件:</p>    <pre>  <code class="language-javascript">/* IMPORT VARIABLES */  @import 'material-design-lite/src/_variables.scss';  /* OVERRIDE VARIABLES */  $layout-screen-size-threshold: 0px;  /* IMPORT MDL */  @import 'material-design-lite/src/material-design-lite.scss';  </code></pre>    <p>引入 JS 文件的方法也很简单,和其他库一样 import 就行:</p>    <pre>  <code class="language-javascript">import 'material-design-lite'  </code></pre>    <p>不过和 materializecss 不同的是,文档里在 《 <a href="/misc/goto?guid=4959674951651499520" rel="nofollow,noindex">Use MDL on dynamic websites</a> 》 写清楚了动态更新页面后的更新方法:</p>    <pre>  <code class="language-javascript">// upgrade element  componentHandler.upgradeElement(button)  // upgrade dom  componentHandler.upgradeDom()  </code></pre>    <p>于是在 vue 的项目里,只要这么写就可以了:</p>    <pre>  <code class="language-javascript"><template>   ..  </template>    <script type="text/babel">    export default {   ready: () => {   componentHandler.upgradeDom()   },   components: {   ...   },   }  </script>    <style lang="scss" rel="stylesheet/scss">   </style>  </code></pre>    <p>谷歌爸爸!受我一拜!</p>    <h3>业务功能</h3>    <p>接下来就是业务部分,计算器嘛还是挺简单的,感觉最有趣的部分是实时更新计算结果,这个功能通过 vue 这种数据绑定框架来实现真是太合适了。</p>    <p>核心代码是这样的,先用 v-for 组装表单控件:</p>    <pre>  <code class="language-javascript"><div>  <div class="mdl-tabs mdl-js-tabs">   <div class="mdl-tabs__tab-bar">   <a v-on:click.prevent="clickTab(0)">沪市 A 股</a>   <a v-on:click.prevent="clickTab(1)">沪市 B 股</a>   <a v-on:click.prevent="clickTab(2)">深市 A 股</a>   <a v-on:click.prevent="clickTab(3)">深市 B 股</a>   </div>   <div>   <div v-for="item of currentTabItem">   <div>   <input type="text" id="{{ item.id }}" v-model="item.model"/>   <label for="{{ item.id }}">{{ item.label }}</label>   <span>{{ item.suffix }}</span>   </div>   </div>   <div id="result-panel" class="mdl-color-text--primary">   {{ result }}   </div>   </div>  </div>  </div>  </code></pre>    <p>然后在 JS 里绑定数据:</p>    <pre>  <code class="language-javascript">export default{   ready: function (){   componentHandler.upgradeDom()   },   methods: {   clickTab: function (index){   this.currentTab = index   this.$nextTick(() => {   componentHandler.upgradeDom()   })   },   },   data: function (){   return {   currentTab: 0,   items: items,   }   },   computed: {   result: function (){   ...   return `${part1}${part2}`   },   currentTabItem: function (){   return items[this.currentTab]   },   },  }  </code></pre>    <p>这样一旦输入的数字发生改变, model 就会改变,就会触发 result 改变,从而刷新 dom 上的显示结果。</p>    <h2>其他</h2>    <p>其他杂七杂八的东西比较多,拎一些踩过的坑记录一下。</p>    <h3>打包</h3>    <p>Electron 虽然提供了简单快捷的跨平台开发方案,但是并没有简单快捷的跨平台打包方案。从 《 <a href="/misc/goto?guid=4959674951735643793" rel="nofollow,noindex">Electron application packaging</a> 》 来看,打包过程比较繁琐,反正我是划了5次触摸板才滚到了底部。</p>    <p>所幸的是, <a href="/misc/goto?guid=4959674951809962193" rel="nofollow,noindex">electron-packager</a> 帮我们解决了这个问题,这个工具已经包含在了前面的了 <a href="/misc/goto?guid=4959674951187836126" rel="nofollow,noindex">electron-boilerplate-vue</a> 里,现在打包只需以下命令即可:</p>    <pre>  <code class="language-javascript">"package": "node build/package.js",  "package:osx": "node build/package.js --platform=darwin",  "package:mas": "node build/package.js --platform=mas",  "package:win": "node build/package.js --platform=win32",  "package:linux": "node build/package.js --platform=linux",  "release": "npm run build && npm run package",  </code></pre>    <p>签名也很容易,只要加上 osx-sign 的选项即可:</p>    <pre>  <code class="language-javascript">if (packagerConfig.platform === 'mas') {   Object.assign(packagerConfig, {   'app-bundle-id': appManifest.bundleId,   'osx-sign': true,   })  }  </code></pre>    <p>打包之后的结果是 .app 文件,如果需要上传到 Mac App Store 可以用 <a href="/misc/goto?guid=4959674951904567687" rel="nofollow,noindex">electron-osx-flat</a> 将其转化成 .pkg 文件,然后通过 Xcode 里的 Application Loader 上传。</p>    <p>然而,并没那么省心。。。打包之后提交审核,审核人员说你这应用打开白屏了。我自己打开一看还真是。。。感觉是 osx-sign 导致的问题,于是就不打 mas 的包了,还是通过手动写脚本的方式签名,参照 <a href="/misc/goto?guid=4959674951992611864" rel="nofollow,noindex">Mac App Store Submission Guide</a> 即可。</p>    <h3>图标</h3>    <p>图标问题也卡了几个小时,主要是因为提交到 iTunes 之后提示说,需要提供 512 和 512@2 的 icns 文件,而打包结果里没找着。</p>    <p>这就奇怪了,我明明放进去了为什么它找不到呢?后来我仔细看了下我的 icns 文件发现是 png 转 icns 的时候出了问题。</p>    <p>在我使用 <a href="/misc/goto?guid=4959659000098172451" rel="nofollow,noindex">iconvert icons</a> 的时候,导出的图片没有 1024*1024 的图:</p>    <p><img src="https://simg.open-open.com/show/32e7ed11ba2e1c64a929252e79ab6419.jpg"></p>    <p>而在我使用 <a href="/misc/goto?guid=4959674952096532328" rel="nofollow,noindex">cloudconvert</a> 的时候,则只有 1024*1024 的图:</p>    <p><img src="https://simg.open-open.com/show/95e621f3465e6f64cc3763798aef3852.jpg"></p>    <p>嗯?!</p>    <p><img src="https://simg.open-open.com/show/f8191fc03d5cff8d9ace84de8ce0d1c9.jpg"></p>    <p>这不是坑爹吗!</p>    <p>于是只好手动撸,刚好电脑里有以前做 iOS Icon 时装的 <a href="/misc/goto?guid=4959674952177921953" rel="nofollow,noindex">prepo</a> ,可以将 1024 的 png 导出各个尺寸的图片并且放到 iconset 里:</p>    <p><img src="https://simg.open-open.com/show/e0eaf387b0cc194f88281472a2b04c87.jpg"></p>    <p>然后只要用一行自带的命令就能将它们打包成 icns 文件:</p>    <pre>  <code class="language-javascript">iconutil -c icns icon.iconset  </code></pre>    <p>大功告成。</p>    <h2>小结</h2>    <p>因为最近忙着搬家的事情,这篇小结就暂时写到这里。</p>    <p>总的来看, Webpack+Vue+MDL+Electron 开发跨平台应用是一种很不错的开发体验。</p>    <p>虽然一路踩了很多坑,但是在应用上架的那个时刻。</p>    <p>我只想说。</p>    <p>Excited!</p>    <p> </p>    <p>来自: <a href="/misc/goto?guid=4959674952252338968" rel="nofollow">http://blog.callmewhy.com/2016/07/01/make-calculator-with-electron/</a></p>    <p> </p>