背景
目前主要的平台分类:
- web平台/H5平台
- App平台(Android、IOS)
- 小程序平台(微信、QQ、支付宝、百度、头条)
- 桌面应用平台(Windows、Mac、Linux)
目前主要的跨平台开发技术:
- uni-app(基于vue,跨H5平台、App平台、小程序平台、web平台)
- Taro(支持react及vue,跨H5平台、App平台、小程序平台)
- electron(跨桌面应用平台)
- Flutter(跨App平台)
- React Native(跨App平台)
如何实现全平台开发?
经过一段时间思考后,萌生了一个想法,能否用uni-app与electron实现全平台开发?
uni-app本身跨端技术已经成熟,缺失的是跨桌面应用,而electron主打跨桌面应用技术,两者相结合做出类似桌面版H5或者叫桌面版小程序应该不成问题。
实现过程
一、搭建uni-app脚手架
uni-app项目有两种创建方式,HBuilderX和vue-cli命令行,HBuilderX方式不够灵活,因此决定使用vue-cli命令行方式搭建脚手架。参考:通过vue-cli命令行创建项目
开始前请确保电脑上已经安装了node和vue-cli。
创建uni-app
vue create -p dcloudio/uni-preset-vue 项目名称
选择项目模板
我们只需要一个空项目,所以选择默认模板即可,然后等待模板下载完成。
进入项目目录,安装样式预处理器,uni-app项目推荐安装scss
npm install -D node-sass sass-loader
安装uView,uView是uni-app生态中优秀的UI框架
使用npm形式安装(也可以选择下载后放入项目)参考:npm安装方式配置
npm install uview-ui
引入uView主JS库
在项目根目录中的main.js中,引入并使用uView的JS库,注意这两行要放在import Vue之后// main.js import uView from "uview-ui"; Vue.use(uView);
引入uView的全局SCSS主题文件
在项目根目录的uni.scss中引入此文件/* uni.scss */ @import 'uview-ui/theme.scss';
引入uView基础样式
在App.vue中首行的位置引入,注意给style标签加入lang=”scss”属性<style lang="scss"> /* 注意要写在第一行,同时给style标签加入lang="scss"属性 */ @import "uview-ui/index.scss"; </style>
配置easycom组件模式(easycom可以在页面直接使用组件,无需通过import引入组件)
此配置需要在项目根目录的pages.json中配置,参考:easycom
```javascript // pages.json { “easycom”: { “^u-(.*)”: “uview-ui/components/u-$1/u-$1.vue” },
// 此为本身已有的内容 “pages”: [
// ......
] } ```
- 到此,脚手架的基础配置就完成了,运行
npm run serve
此脚手架仅用于示例,一个完整的脚手架至少还应该包括eslint、请求库、运行环境配置、vue.config配置、工具库等
二、配置Electron环境
在基于vue的项目中我们可以使用vue-cli-plugin-electron-builder
插件很方便地配置electron环境,插件内集成了调试和打包,只需简单配置,就可以在各个桌面平台打包。(装包时需要科学上网,否则调试插件会安装不了,影响调试)
安装vue-cli-plugin-electron-builder
vue-cli-plugin-electron-builder是vue-cli插件,我们需要使用vue-cli安装。
在vue-cli3及以后版本中,我们可以启动图形化界面安装插件:
启动图形化界面后选择对应项目,点击插件-添加插件
在搜索框中输入vue-cli-plugin-electron-builder
并搜索,找到对应插件,点击安装
安装成功后,会跳转到配置插件,让我们选择electron版本,高版本容易出错,所以我们可以选择11.0.0版本,然后点击完成安装
安装完成后我们可以在项目中看到在package.json中添加了相关配置,包括启动脚本,入口文件,依赖包等
vue ui
运行项目
看到下面的画面,就说明安装成功了
npm run electron:serve
需要注意的是,第一次运行时会安装Vue Devtools,如果出现Vue Devtools failed to install :Error:net::ERR_CONNECTION_TIMED_OUT错误,可以忽略,在多次尝试仍然失败后,项目可以正常启动,但是,在控制台中不会显示Vue Devtools,要解决这个问题,建议科学上网。
适配uni-app
我们可以在background.js中看到,electron默认启动的窗口大小为800x600,我们修改为375x667即可
三、OCR识别
脚手架已经搭建完成,需要实战项目检验,本文以实现OCR识别功能为例,验证想法,体现跨平台魅力
要实现OCR功能,需要有相关接口,常用的有百度OCR,腾讯OCR,不过免费版本都有数量限制,所以我找了个相关平台免费的接口来实现
- 在
src/pages/index
下新建upload.vue组件,并写入以下代码 ```javascript
> u-upload是跨平台组件库uview中的上传组件
2. 在`src/pages/index`下的index.vue文件中写入以下代码<br />使用electron运行项目可以看到<br />![1.15.png](https://cdn.nlark.com/yuque/0/2021/png/1847559/1628732121840-e3cd1837-a80a-4912-99e6-a36a352933d3.png#clientId=ue3074002-eab5-4&from=drop&id=u5ce00de5&margin=%5Bobject%20Object%5D&name=1.15.png&originHeight=248&originWidth=398&originalType=binary&ratio=1&size=10954&status=done&style=none&taskId=ud5076854-fdd0-45c0-966c-ecc32206f36)<br />选择一张图片,OCR识别结果如下<br />![1.17.png](https://cdn.nlark.com/yuque/0/2021/png/1847559/1628732137070-3a49279e-510e-4ba0-be78-63c2fb2edbc4.png#clientId=ue3074002-eab5-4&from=drop&id=u2db19b6e&margin=%5Bobject%20Object%5D&name=1.17.png&originHeight=119&originWidth=840&originalType=binary&ratio=1&size=25324&status=done&style=none&taskId=u0a16017e-d441-4bb8-ad2a-b301dc0e91f)<br />![1.16.png](https://cdn.nlark.com/yuque/0/2021/png/1847559/1628732143563-2dd61897-02f9-44a2-af3f-bb91fd06ae38.png#clientId=ue3074002-eab5-4&from=drop&id=u9b6c7a11&margin=%5Bobject%20Object%5D&name=1.16.png&originHeight=392&originWidth=397&originalType=binary&ratio=1&size=63136&status=done&style=none&taskId=u72ca7006-83b7-471f-8572-bae0c0424a8)<br />点击复制会把识别结果复制到剪贴板
```javascript
<template>
<view class="ocr">
<upload :dis_type="dis_type" @success="uploadSuccess" @remove="uploadRemove" />
<view class="textlist">
<view class="textlist_item" v-for="(item, index) in resultList" :key="index">
<u-badge type="error" :absolute="true" :offset="[10, 10]" :overflow-count="999" :count="index + 1"></u-badge>
<view v-for="(it, ix) in item.words_result" :key="ix">{{ it.words }}</view>
<u-button class="copy" type="primary" shape="circle" size="mini" @click="copyText(item)">复制</u-button>
</view>
</view>
</view>
</template>
<script>
import upload from './upload.vue';
export default {
components: { upload },
data: () => ({
dis_type: 1,
resultList: [],
copyValue: ''
}),
computed: {},
watch: {},
methods: {
uploadSuccess(data) {
this.resultList.push(data.data);
},
uploadRemove(index) {
this.resultList.splice(index, 1);
},
copyText(item) {
this.copyValue = '';
item.words_result.forEach(v => {
this.copyValue += `${v.words} `;
});
// #ifdef H5
const oInput = document.createElement('input');
oInput.value = this.copyValue;
document.body.appendChild(oInput);
oInput.select(); // 选择对象
document.execCommand('Copy'); // 执行浏览器复制命令
oInput.className = 'oInput';
oInput.style.display = 'none';
uni.showToast({
title: '复制成功',
duration: 2000,
mask: true
});
// #endif
// #ifndef H5
uni.setClipboardData({
data: this.copyValue,
success: () => {
uni.showToast({
title: '复制成功',
duration: 2000,
mask: true
});
}
});
// #endif
}
},
// 页面周期函数--监听页面加载
onLoad(options) {},
// 页面处理函数--监听用户下拉动作
onPullDownRefresh() {
uni.stopPullDownRefresh();
}
};
</script>
<style lang="scss" scoped>
.ocr {
padding-top: 30rpx;
.textlist {
padding: 10rpx;
&_item {
position: relative;
border: 1rpx solid #eee;
padding: 20rpx;
margin-bottom: 30rpx;
.copy {
margin-top: 20rpx;
}
}
}
}
</style>
- 跨域问题
如果你按照上面的步骤成功运行了项目,你会发现并没有这么顺利,请求会报错,不会返回识别结果,这是因为接口跨域了,在小程序平台、APP平台不存在这个问题,web平台、H5平台如何解决跨域问题不在赘述,在electron中,我们可以在background.js
中加入webSecurity: false
解决// background.js async function createWindow() { // Create the browser window. const win = new BrowserWindow({ width: 375, height: 667, webPreferences: { // Use pluginOptions.nodeIntegration, leave this alone // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node- integration for more info nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION, contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION, webSecurity: false // 解决跨域问题 } }) ... }
四、全平台运行
桌面应用平台
npm run electron:serve
web平台/H5平台
npm run serve or npm run dev:h5
小程序平台 ```bash // 微信小程序 npm run dev:mp-weixin
// 支付宝小程序 npm run dev:mp-alipay
// 头条小程序 npm run dev:mp-toutiao
4. App平台<br />App平台运行相对复杂,需要借助HBuilderX运行到真机或虚拟机上,在HBuilderX中打开项目,在菜单栏选择运行-运行到手机或模拟器,选择对应设备运行<br />![1.18.png](https://cdn.nlark.com/yuque/0/2021/png/1847559/1628732272604-d31ef91a-3c68-4e90-9265-ea094eebad8f.png#clientId=ue3074002-eab5-4&from=drop&id=uc1e5713b&margin=%5Bobject%20Object%5D&name=1.18.png&originHeight=387&originWidth=876&originalType=binary&ratio=1&size=149551&status=done&style=none&taskId=ub3f6cc34-1534-4ab8-afca-219b055d764)<br />安卓模拟器效果<br />![1.20.png](https://cdn.nlark.com/yuque/0/2021/png/1847559/1628732280169-765c0b34-67f3-4895-aa24-5c753eca515a.png#clientId=ue3074002-eab5-4&from=drop&id=u17b7a750&margin=%5Bobject%20Object%5D&name=1.20.png&originHeight=692&originWidth=962&originalType=binary&ratio=1&size=40277&status=done&style=none&taskId=u17a20df5-9f25-49fd-8af8-1deada67f28)
> 在小程序平台和App平台上传图片可以选择相册或者拍照
5. 全平台集成运行<br />其实所有平台都可以在HBuilderX中运行,在HBuilderX运行菜单中,可以运行到浏览器、各家小程序、手机和模拟器,运行到终端菜单就是执行npm命令,特别的是HBuilderX内置浏览器不存在跨域问题,更方便开发<br />![1.19.png](https://cdn.nlark.com/yuque/0/2021/png/1847559/1628732300924-df168e78-dfc2-4da3-a429-9a14b9bf81ae.png#clientId=ue3074002-eab5-4&from=drop&id=u270d2b26&margin=%5Bobject%20Object%5D&name=1.19.png&originHeight=385&originWidth=891&originalType=binary&ratio=1&size=180652&status=done&style=none&taskId=u93fa20c6-177a-4a39-88f4-2648c3aea84)
<a name="fdfad493"></a>
#### 五、全平台打包
1. 桌面应用平台
```bash
npm run electron:build
web平台/H5平台
npm run build or npm run build:h5
小程序平台 ```bash // 微信小程序 npm run build:mp-weixin
// 支付宝小程序 npm run build:mp-alipay
// 头条小程序 npm run build:mp-toutiao ```
- App平台
App平台打包同样也相对复杂,需要借助HBuilderX打包成App应用,在HBuilderX中打开项目,在菜单栏选择发行-原生App-云打包,在打开的弹窗中选择需要的平台,并完成相关配置,然后点击打包按钮,项目打包配置检查通过后,会在云端打包,打包完成后,在HBuilderX的终端中可以看到应用下载位置
全平台集成打包
同全平台集成运行一样,在HBuilderX运行菜单中,运行到终端菜单就是执行npm命令,选择对应打包命令即可打包(App平台除外)
六、效果展示
桌面应用平台 | Windows (win 10) | Mac | Linux (unbutu) | | —- | —- | —- | | | | |
web平台/H5平台
小程序平台
| 微信小程序 | QQ小程序 | 头条小程序 | | —- | —- | —- | | | | |App平台
| Android(android 10) | IOS(ios 12) | | —- | —- | | | |