Wscats / articles

🔖My Learning Notes and Memories - 分享我的学习片段和与你的回忆
https://github.com/Wscats/articles
3.17k stars 735 forks source link

Cordova配置(WebApp混合开发环境)&&Hbuilder打包配置 #48

Closed Wscats closed 5 years ago

Wscats commented 8 years ago

必备环境安装

1.配置JDK和Gradle环境

用的是1.8的版本,网上很多地方可以下载,这里不上链接了 在本地配置sdk变量,如图,点击桌面(计算机)->右键属性->高级系统设置->系统属性面板高级->点击环境变量->在下面框中的系统变量中新建 这里写图片描述

JAVA_HOME          D:\Program Files\Java\jdk1.8.0_112//这个路径根据你安装JDK的环境对应其地址
PATH                      %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;//PATH本来是有内容的,不要全部替换,用分号隔开,再往后继续添加
CLASSPATH            .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar//注意不要漏了前面的点

//新版本需要配置如下Gradle环境
GRADLE_HOME      D:\Android_Studio\gradle\gradle-2.14.1-all //指向gradle的已解压文件的根目录配置path:
PATH                      %GRADLE_HOME%\bin //继续往PATH后面添加环境变量

2.下载Android SDK

推荐国内的android-studio下载 然后配置Android SDK环境,ANDROID_HOME定位到你的SDK文件夹的根目录

ANDROID_HOME     D://android-sdk-windows     //对应你的SDK文件夹的目录

重启电脑

Cordova安装

1.下载Node.js

node官网

2.在mac终端运行下面命令,输入密码安装cordova

sudo npm install -g cordova

如果是Window系统,去掉sudo

npm install -g cordova

这里写图片描述 sudo是因为需要管理员权限,-g是全局安装 cordvoa官网命令行帮助

3.执行以下代码创建一个cordvoa应用

cordova create hello com.example.hello HelloWorld

第一个参数是文件目录,第二个参数是app id, 第三个参数是显示的title 这里写图片描述

IOS打包

4.定位到hello目录文件夹,为项目安装平台模块

cd hello
cordova platform add ios

这里写图片描述

5.打开xcodeproj项目的文件位置双击打开,如果已安装Xcode就能顺利的打开项目了

hello/platform/ios/ 这里写图片描述

6.可以用编写html项目的IDE打开www下的index.html查看效果,在浏览器打开即可

这里写图片描述

7.在Xcode打开iOS 模拟器如果看到以下效果说明环境已经搭建成功

这里写图片描述

ANDROID打包

续前面三步

4.定位到hello目录文件夹,为项目安装平台模块

cd hello
cordova platform add android

5.生成APK文件

cordova build android

image 遇到这个问题就是gradle下载失败了,可以尝试拿图中的链接手动下载然后把它放到对应的系统文件下,如下,注意版本一定要对应上

C:\Users\Administrator\.gradle\wrapper\dists\gradle-2.14.1-all\4cj8p00t3e5ni9e8iofg8ghvk7

image gradle-2.14.1-all.zip下载地址 或者android-studio下载 成功就会显示如下 image apk的目录如下:

项目路径\test\platforms\android\build\outputs\apk

6.更改应用桌面图标

在cordova生成项目的跟目录创建res文件夹 image 然后更改config.xml文件

ldpi : 36x36 px mdpi : 48x48 px hdpi : 72x72 px xhdpi : 96x96 px xxhdpi : 144x144 px xxxhdpi : 192x192 px

<platform name="android">
        <allow-intent href="market:*" />
        <icon src="res/android/36.png" density="ldpi" />
        <icon src="res/android/48.png" density="mdpi" />
        <icon src="res/android/72.png" density="hdpi" />
        <icon src="res/android/96.png" density="xhdpi" />
        <icon src="res/android/144.png" density="xxhdpi" />
        <icon src="res/android/192.png" density="xxxhdpi" />
    </platform>

7.调用相机

cordova plugin add cordova-plugin-camera

安装成功就会在plugin出现两个文件夹cordova-plugin-cameracordova-plugin-compat image

Wscats commented 8 years ago

其他注意事项

这里写图片描述 如果遇到我上面的情况,那就是xcode冲命名或者路径不对,因为我用了xcode-beta版本,所以我重新设置了路径就能正常使用了

Wscats commented 7 years ago

HBuilder 打包流程

1.运行HBuilder

百度搜索HBuilder,官网下载安装包,解压,运行HBuilder.exe。注册账号,并登陆 image

2.新建App

在左边右键,选择新建APP,或者点击中间的新建app image

3.填入信息

在弹出的窗口,填应用名称,根据需求选择项目位置,以及模板内容,注意名字不要带有@等特殊字符,最后点击完成 image

4.真机调试

创建好之后,选择刚刚创建好的项目,在顶部选择运行,根据你的情况现在运行方式 image

5.真机演示

这是我刚刚选择的模板app展示,功能齐全,基本涵盖安卓常用的底层接口,如摄像头,地图,震动,下载等功能 这里写图片描述

6.打包成app

选择要打包的项目,在顶部选择运行,发布原生APK安装包 image

7.云端打包

在弹出的窗口,选择相应证书,如果参数配置未完成,点击顶部参数配置 image

8.图标配置

上传图标,如果不想逐个逐个图标替换,我们可以点击下方生成并替换 image

9.参数配置

这里的SDK配置选项要填上去才能打包,你可以暂时随机填一些乱串上去,最后就是保证所有提醒红叉的地方都要填满,微信登录,微信支付和微信消息及朋友圈的appid三者要统一,其他在测试时可随意

image

10.打包成app---选择要打包的项目,在顶部选择运行,发型原生安装包

image

11.云端打包

在弹出的窗口,选择相应证书,如果参数配置未完成,点击顶部参数配置,如果配置完成,点击底部打包 image

12.稍等一会儿

image image

13.如果刚刚不小心关闭了,或者后面某天想找到打包的App,在顶部选择运行,根据需要选择查看。发送到手机上,安装试试

image

Wscats commented 7 years ago

其他注意事项

真机调试过程中,如果出现如下错误提醒

安装失败,失败原因:Failure [INSTALL_CANCELED_BY_USER]。 
请手动点亮手机屏幕,并重新运行真机调试,注:可能需要在手机上确认安装。

勾选了以下几个选项就可以调试了 image

调用plus

之前试过用Vue写的项目,用hbuilder打包APP,在页面第一次加载的时候无法获取cookie,storage等 后来发现原来是一定要plus加载完毕之后才能获取对应的信息,所以如果用了其他框架写的项目不要直接加载对应的JS,可以改成这样

function plusReady() {
    var script = document.createElement("script");
    script.src = "main-fa9faab1705d2e80a7c5.js";//项目的JS
    document.body.appendChild(script);
    console.log(plus.storage.getItem("token"))
}
document.addEventListener("plusready", plusReady, false);//等待plus准备就绪在加载

H5+支付接口

这里的官方文档的支付插件配置的请求DEMO是错误的,注意是用 POST 请求,而非 GET 请求

var channel = null;
// 1. 获取支付通道
function plusReady() {
    // 获取支付通道
    plus.payment.getChannels(function (channels) {
        channel = channels[0];
    }, function (e) {
        alert("获取支付通道失败:" + e.message);
    });
}
document.addEventListener('plusready', plusReady, false);

var ALIPAYSERVER = 'http://demo.dcloud.net.cn/helloh5/payment/alipay.php';
var WXPAYSERVER = 'http://demo.dcloud.net.cn/helloh5/payment/wxpay.php';
// 2. 发起支付请求
function pay(id) {
    // 从服务器请求支付订单
    var PAYSERVER = '';
    if (id == 'alipay') {
        PAYSERVER = ALIPAYSERVER;
    } else if (id == 'wxpay') {
        PAYSERVER = WXPAYSERVER;
    } else {
        plus.nativeUI.alert("不支持此支付通道!", null, "捐赠");
        return;
    }
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
        switch (xhr.readyState) {
            case 4:
                if (xhr.status == 200) {
                    plus.payment.request(channel, xhr.responseText, function (result) {
                        plus.nativeUI.alert("支付成功!", function () {
                            back();
                        });
                    }, function (error) {
                        plus.nativeUI.alert("支付失败:" + error.code);
                    });
                } else {
                    alert("获取订单信息失败!");
                }
                break;
            default:
                break;
        }
    }
    xhr.open('POST', PAYSERVER);
    //如果是POST请求方式,设置请求首部信息
    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xhr.send("total=0.01");
}
Wscats commented 5 years ago

调用定位

调用定位可选用这个方法plus.geolocation.getCurrentPosition

location() {
    var that = this;
    if (window.plus) {
        plus.geolocation.getCurrentPosition(
            function (p) {
                that.latitude = p.coords.latitude;
                that.longitude = p.coords.longitude;
            },
            function (e) {
                alert("Geolocation error: " + e.message);
            }
        );
    }
}

当然我们可以把经纬度直接发到后台记录,实现实时跟踪定位

sendLocation() {
    var that = this;
    axios
        .get("http://10.3.136.180:9999/sendPostion", {
            params: {
                latitude: that.latitude,
                longitude: that.longitude
            }
        })
        .then(data => {
            console.log(data);
        })
        .catch(() => {});
}

录音

调用plus.audio.getRecorder()获取音频对象,可以在成功的回调里面获取录音的地址

mounted() {
    this.r = plus.audio.getRecorder();
},
startRecord() {
    if (this.r == null) {
        alert("Device not ready!");
        return;
    }
    this.r.record({
            filename: "_doc/audio/"
        },
        e => {
            console.log(e);
            this.path = e;
            alert("Audio record success!");
        },
        e => {
            alert("Audio record failed: " + e.message);
        }
    );
}

播放

可以在上面把获取到的录音地址放进来用plus.audio.createPlayer(this.path)打开,然后播放音频信息,当然也可以播放其他格式的音频信息

startPlay() {
    console.log(this.path);
    if (plus.audio == undefined) {
        alert("Device not ready!");
    }
    this.p = plus.audio.createPlayer(this.path);
    this.p.play(
        function () {
            alert("Audio play success!");
        },
        function (e) {
            alert("Audio play error: " + e.message);
        }
    );
}

暂停播放

stopRecord() {
    this.r.stop();
}

上传文件

前端上传逻辑可以直接使用H5的input配合FormData格式

uploadImg() {
    var fileNode = document.getElementById("file");
    var xmlhttp = new XMLHttpRequest();
    //设置回调,当请求的状态发生变化时,就会被调用
    xmlhttp.onreadystatechange = function () {
        //上传成功,返回的文件名,设置到父节点的背景中
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            console.log(xmlhttp.responseText);
        }
    };
    //构造form数据
    var data = new FormData();
    console.log(fileNode.files);
    data.append("avatar", fileNode.files[0]);
    console.log(data);
    //设置请求,true:表示异步
    xmlhttp.open("post", "http://10.3.136.180:9999/requireImg", true);
    //不要缓存
    //xmlhttp.setRequestHeader("If-Modified-Since", "0");
    //提交请求
    xmlhttp.send(data);
    //清除掉,否则下一次选择同样的文件就进入不到onchange函数中了
    fileNode.value = null;
}
<input @change="uploadImg" type="file" id="file" name="avatar">

后端逻辑,配合 express 和 multer 模块可以获取到传过来的文件

var express = require("express");
var multer = require('multer')
var upload = multer({
    dest: 'uploads/'
})
var app = express();
app.post("/requireImg", upload.single('avatar'), (req, res) => {
    res.append("Access-Control-Allow-Origin", "*")
    res.send({
        state: "success"
    })
})
app.listen(9999)
Wscats commented 5 years ago

uni

uni-app是一个使用Vue.js开发跨平台应用的前端框架,开发者编写一套代码,可编译到iOS、Android、H5、小程序等多个平台。

目录结构

一个uni-app工程,默认包含如下目录及文件:

┌─components            uni-app组件目录
│  └─comp-a.vue         可复用的a组件
├─hybrid                存放本地网页的目录,详见
├─platforms             存放各平台专用页面的目录,详见
├─pages                 业务页面文件存放的目录
│  ├─index
│  │  └─index.vue       index页面
│  └─list
│     └─list.vue        list页面
├─static                存放应用引用静态资源(如图片、视频等)的地方,注意:静态资源只能存放于此
├─main.js               Vue初始化入口文件
├─App.vue               应用配置,用来配置App全局样式以及监听 应用生命周期
├─manifest.json         配置应用名称、appid、logo、版本等打包信息
└─pages.json            配置页面路由、导航条、选项卡等页面类信息

全局样式库

可以在App.vue组件里面引入uni.css定义好的样式,配合已经写好的组件和模板快速开发

<style>
    /* uni.css - 通用组件、模板样式库,可以当作一套ui库应用 */
    @import "./common/uni.css";
</style>

应用生命周期

函数名 说明
onLaunch 当uni-app 初始化完成时触发(全局只触发一次)
onShow 当 uni-app 启动,或从后台进入前台显示
onHide 当 uni-app 从前台进入后台 浏览器页面切换时候会触发,Mac显示屏切换会触发
onUniNViewMessage 对 nvue 页面发送的数据进行监听,可参考 nvue 向 vue 通讯

注意

页面生命周期

在 js 中定义onPullDownRefresh处理函数(和onLoad等生命周期函数同级),监听该页面用户下拉刷新事件。需要在pages.json里,找到的当前页面的 pages 节点,并在 style 选项中开启enablePullDownRefresh。当处理完数据刷新后,uni.stopPullDownRefresh可以停止当前页面的下拉刷新。

onPullDownRefresh() {
    setTimeout(() => {
        uni.stopPullDownRefresh()
    }, 1000)
}

触底触发的生命周期

onReachBottom() {
    console.log('到底了')
}

pages.json

配置路由和底部的tabbar,和小程序是差不多的,注意当我们在page文件夹新建页面的时候,pages.json文件的pages内容会自动添加

{
    "pages" : [
        //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
        {
            "path" : "pages/index/index",
            "style" : {
                "navigationBarTitleText" : "uni-app"
            }
        },
        {
            "path" : "pages/home/home",
            "style" : {
                "navigationBarTitleText" : "设置页"
            }
        },
        {
            "path" : "pages/detail/detail",
            "style" : {}
        }
    ],
    "globalStyle" : {
        "navigationBarTextStyle" : "black",
        "navigationBarTitleText" : "uni-app",
        "navigationBarBackgroundColor" : "#F8F8F8",
        "backgroundColor" : "#F8F8F8"
    },
    "tabBar" : {
        "color" : "#7A7E83",
        "selectedColor" : "#007AFF",
        "borderStyle" : "black",
        "backgroundColor" : "#ffffff",
        "list" : [
            {
                "pagePath" : "pages/index/index",
                "iconPath" : "static/component.png",
                "selectedIconPath" : "static/componentHL.png",
                "text" : "组件"
            },
            {
                "pagePath" : "pages/home/home",
                "iconPath" : "static/component.png",
                "selectedIconPath" : "static/componentHL.png",
                "text" : "组件"
            }
        ]
    }
}

路由跳转

声明式导航:用绝对路径或者相对当前组件的相对路径,这里的路由跳转用了绝对路径

<navigator url="/pages/detail/detail" hover-class="navigator-hover"></navigator>
<navigator url="navigate/navigate?title=navigate" hover-class="navigator-hover"></navigator>

编程式导航

// 保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返回到原页面。
uni.navigateTo({
    url: 'test?id=1&name=uniapp'
});
// 关闭当前页面,跳转到应用内的某个页面。
uni.redirectTo({
    url: 'test?id=1'
});
// 关闭所有页面,打开到应用内的某个页面。
uni.reLaunch({
    url: 'test?id=1'
});
// 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。
uni.switchTab({
    url: '/pages/index/index'
});
// 关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层
// 此处是A页面
uni.navigateTo({
    url: 'B?id=1'
});
// 此处是B页面
uni.navigateTo({
    url: 'C?id=1'
});
// 在C页面内 navigateBack,将返回A页面
uni.navigateBack({
    delta: 2
});

组件

可以使用uni-app帮我封装好的组件,但有几个比较注意的是,组件的名字不要用一些微信小程序或者一些常用的名字,我们可以在前面加个后缀,防止无效

<template>
    <view>
        <xswiper />
        <xaudio />
        <xmediaList />
        <xmap />
    </view>
</template>
import xaudio from '../../components/audio/audio.vue'
import xmap from '../../components/map/map.vue'
import xswiper from '../../components/swiper/swiper.vue'
import xmediaList from '../../components/media-list/media-list.vue'

发起请求

可以使用uni.request发起请求

const requestUrl = "https://cnodejs.org/api/v1/topics"
const duration = 2000
export default {
    data() {
        return {
            loading: false,
            res: ""
        }
    },
    methods: {
        makeRequest: function() {
            this.loading = true
            uni.request({
                url: requestUrl,
                data: {
                    noncestr: Date.now()
                },
                success: (res) => {
                    uni.showToast({
                        title: '请求成功',
                        icon: 'success',
                        mask: true,
                        duration: duration
                    })
                    this.res = '请求结果 : ' + JSON.stringify(res);
                    console.log('request success', res)
                },
                fail: (err) => {
                    console.log('request fail', err);
                    uni.showModal({
                        content: err.errMsg,
                        showCancel: false
                    })
                },
                complete: () => {
                    this.loading = false
                }
            })
        }
    }
}

调用微信小程序接口

这里注意要把小程序的单向数据绑定和指令改为vue的写法,也可以使用uni.createCameraContext()兼容性会更好

<template>
    <view>
        <text>text</text>
        <camera device-position="back" flash="off" binderror="error" style="width: 100%; height: 300px;"></camera>
        <button type="primary" @click="takePhoto">拍照</button>
        <view>预览</view>
        <image mode="widthFix" :src="src"></image>
    </view>
</template>
<script>
    export default {
        data() {
            return {
                src:""
            };
        },
        methods: {
            takePhoto() {
                const ctx = wx.createCameraContext()
                ctx.takePhoto({
                    quality: 'high',
                    success: (res) => {
                        this.src = res.tempImagePath
                    }
                })
            },
        }
    }
</script>