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

背景

Electron 是一个跨平台应用框架,原本为 Atom 设计,后来单独分离了出来。它提供了一些与原生系统交互的 API ,可以方便快速使用 HTML、JavaScript、CSS 等 Web 技术开发跨平台应用。

最近基于 Electron 开发了一个简单的理财计算器。发布了 1.0 版本之后,总结一下这段时间的开发经历。哦对了,价格是随便定的,主要是熟悉下流程,大家千万别买。

这篇文章只包含个人开发小结,不包括各种框架工具的基础使用,如有需要请自行 Google 。

完整的项目源码请看:financial-calculator

过程

理财计算器的功能很简单,目前只做了股票的保本卖出价的计算。

输入买入价、买入数量等信息,算上各种手续费之后,输出保本卖出的价格。

看起来很简单的功能,大概却做了5天,主要在开发环境配置上卡了挺久。

基础项目

一开始需要做的是搭建整个项目的基础框架,主要包含以下几个方面:

  • electron:跨平台框架,主要是封装 Web 应用,并且打包发布。
  • webpack:模块加载打包工具,可以通过各种 loader 加载不同资源。
  • eslint:JS 代码静态检测工具,可以方便的统一代码的风格。
  • babel:可以将 ES6 代码转为 ES5 代码的语法编译器。
  • vue:前端框架,数据驱动的组件化开发,配上 vue-loader 之后十分好用。

上面这一套东西要搞一下还是比较费时间的,好在网上有各种现成的模板,货比三家之后最后选用了 electron-boilerplate-vue ,搭建好后文件目录如下:

.
├── .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

前端页面

接下来就是前端页面部分。一直想试试 Material Design 风格,刚好这次是个不错的机会,于是就开始找找有没有 MD 方面的前端库。

先是想看看有没有类似于 Ant Design 这样的成套 vue 组件库,于是找到了 material-ui ,可惜是 React 版本的。找找了 vue 版本的似乎只有 vue-mdlvue-material 这两个不太成气候的库,仔细想想本身它们都依赖于其他前端库和 vue ,万一更新不及时岂不是很坑爹。于是弃了这个想法。

后来就是去找类似于 semantic-uiFoundation 这样纯粹的前端库,找到 materializecss 这个库,页面简洁大方,破儿费(Perfect)啊!

然后,就是为期两天的填坑之旅。

是的,一共5天的开发时间,调教这个前端库花了2天,而且坑爹的是,最后还没用上。主要问题在于它的 JS 代码是基于 jQuery 的,而很多插件又是在 $(document).ready 里加载的,对 SPA 非常不友好,因为 document ready 的时候我的 dom 们还没 ready 。再加上我对 Webpack 又不是很熟悉,导致点击事件不响应,不知道是 webpack 打包的问题还是 vue-loader 加载顺序的问题还是 babel-loader 编译的问题还是它自己库的问题还是 jquery 引入的问题。

总之就是,一套系统过于庞大,虽然脚手架让我避开了大量的体力劳动,但是同时也引入了很多我不熟悉的未知因素。于是就刚好花两天时间,仔细学习了项目中用到的各种技术和工具。熟悉了之后才发现我以前多虑了,问题肯定出在源码上。然后就找到了问题的原因。

最后决定放弃 materializecss 这个库,然后去试了试 Google 家的 Material Design Lite

万万没想到,5分钟解决战斗。

引入 CSS 的方法和 materializecss 类似,都是在 SCSS 里 import 它的原文件:

/* 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';

引入 JS 文件的方法也很简单,和其他库一样 import 就行:

import 'material-design-lite'

不过和 materializecss 不同的是,文档里在 《Use MDL on dynamic websites》 写清楚了动态更新页面后的更新方法:

// upgrade element
componentHandler.upgradeElement(button)
// upgrade dom
componentHandler.upgradeDom()

于是在 vue 的项目里,只要这么写就可以了:

<template>
..
</template>
<script type="text/babel">
export default {
ready: () => {
componentHandler.upgradeDom()
},
components: {
...
},
}
</script>
<style lang="scss" rel="stylesheet/scss">
</style>

谷歌爸爸!受我一拜!

业务功能

接下来就是业务部分,计算器嘛还是挺简单的,感觉最有趣的部分是实时更新计算结果,这个功能通过 vue 这种数据绑定框架来实现真是太合适了。

核心代码是这样的,先用 v-for 组装表单控件:

<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>

然后在 JS 里绑定数据:

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]
},
},
}

这样一旦输入的数字发生改变, model 就会改变,就会触发 result 改变,从而刷新 dom 上的显示结果。

其他

其他杂七杂八的东西比较多,拎一些踩过的坑记录一下。

打包

Electron 虽然提供了简单快捷的跨平台开发方案,但是并没有简单快捷的跨平台打包方案。从 《Electron application packaging》 来看,打包过程比较繁琐,反正我是划了5次触摸板才滚到了底部。

所幸的是, electron-packager 帮我们解决了这个问题,这个工具已经包含在了前面的了 electron-boilerplate-vue 里,现在打包只需以下命令即可:

"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",

签名也很容易,只要加上 osx-sign 的选项即可:

if (packagerConfig.platform === 'mas') {
Object.assign(packagerConfig, {
'app-bundle-id': appManifest.bundleId,
'osx-sign': true,
})
}

打包之后的结果是 .app 文件,如果需要上传到 Mac App Store 可以用 electron-osx-flat 将其转化成 .pkg 文件,然后通过 Xcode 里的 Application Loader 上传。

然而,并没那么省心。。。打包之后提交审核,审核人员说你这应用打开白屏了。我自己打开一看还真是。。。感觉是 osx-sign 导致的问题,于是就不打 mas 的包了,还是通过手动写脚本的方式签名,参照 Mac App Store Submission Guide 即可。

图标

图标问题也卡了几个小时,主要是因为提交到 iTunes 之后提示说,需要提供 512 和 512@2 的 icns 文件,而打包结果里没找着。

这就奇怪了,我明明放进去了为什么它找不到呢?后来我仔细看了下我的 icns 文件发现是 png 转 icns 的时候出了问题。

在我使用 iconvert icons 的时候,导出的图片没有 1024*1024 的图:

而在我使用 cloudconvert 的时候,则只有 1024*1024 的图:

嗯?!

这不是坑爹吗!

于是只好手动撸,刚好电脑里有以前做 iOS Icon 时装的 prepo ,可以将 1024 的 png 导出各个尺寸的图片并且放到 iconset 里:

然后只要用一行自带的命令就能将它们打包成 icns 文件:

iconutil -c icns icon.iconset

大功告成。

小结

因为最近忙着搬家的事情,这篇小结就暂时写到这里。

总的来看, Webpack+Vue+MDL+Electron 开发跨平台应用是一种很不错的开发体验。

虽然一路踩了很多坑,但是在应用上架的那个时刻。

我只想说。

Excited!