×
您的位置: 首页 > 编程笔记

移动端App uni-app + mui 开发记录

移动端 uni-app MUI 混合开发 时间:2019-12-03  查看:2746   收藏

  前言

  1、uni-app

  uni-app是DCloud推出的终极跨平台解决方案,是一个使用Vue.js开发所有前端应用的框架,官网:https://uniapp.dcloud.io/

 

  2、mui

  号称最接近原生APP体验的高性能前端框架,官网:https://dev.dcloud.net.cn/mui/

  个人觉得,mui除了页面设计很接近原生App之外,还有一个特点就是能方便的使用App扩展规范Html5 Plus(http://www.html5plus.org/doc/h5p.html),我们能在它的源码中看到比较多的地方都有使用到

 

  3、开发工具

  使用HBuilderX开发工具写uni-app的代码,以及打包App等工作,主要的业务功能依旧是使用我们熟悉的idea开发,不过页面从webPC端风格改成了移动端风格

 

   4、整体架构

  我们采用uni-app + mui的方式,使用的是官方推荐的 uni-app原生标题栏跟导航栏 + 嵌入webview远程服务的页面,也就是说除了头部、尾部,中间的内容都是类似iframe嵌入进去

  简单的说,uni-app,包括头部标题栏、底部导航栏作为App的“壳”,java后端+mui前端页面作为App的“内容”,这样选型的目的是为了方便后期的运维、升级

  webview嵌入:直接升级后端服务并重新部署即可,无需重新打包、升级App

  头尾使用原生组件:提升App流畅度

   

  为方便以后查阅,特此记录

 

  uni-app部分

  我在App.vue中对uni对象进行全局赋值,这样在每个页面都调用到,这样做的目的是为了方便全局修改,比如全局该监听方法、后期需要换进度条样式、更换后端服务地址等

   

  tabBar导航栏

  底部的导航栏比较简单,在page.json进行配置就可以

 

  page.json

{"pages": [//pages数组中第一项表示应用启动页        {"path": "pages/index/index","style": {"navigationBarTitleText": "首页","titleNView": {"buttons": [{"type": "none","float": "left"}, {"type": "none","float": "right","fontSrc":"/static/fonts/mui.ttf"}]
                }
            }
        }
    ],"globalStyle": {"navigationBarTextStyle": "black","navigationBarTitleText": "","navigationBarBackgroundColor": "#F8F8F8","backgroundColor": "#F8F8F8","backgroundColorTop": "#F4F5F6","backgroundColorBottom": "#F4F5F6"},"tabBar": {"color": "#7A7E83","selectedColor": "#007AFF", //#007AFF 蓝色  #f07837 橙色"borderStyle": "black","backgroundColor": "#F8F8F8","list": [{"pagePath": "pages/index/index","iconPath": "static/image/index/index_.png","selectedIconPath": "static/image/index/index.png","text": "首页"}],"position": "bottom"}
}

View Code

   

  监听标题栏按钮

  设置进度条颜色

  设置进度条颜色、监听webview的url变化判断是否需要标题栏按钮等操作全都在App.vue中进行,具体页面可以直接调用样式对象、监听方法

  App.vue

<script>export default {
        onLaunch: function() {//应用加载后初始后端服务地址uni.phoneServiceAddress = "http://qch2.vipgz2.idcfengye.com"; //为了方便App演示,这里开了一个内网穿透//监听软键盘高度变化,隐藏或显示tabbaruni.onKeyboardHeightChange(res => {if (res.height > 0) {
                    uni.hideTabBar();
                } else {
                    uni.showTabBar();
                }
            })//全局进度条样式uni.webviewStyles = {
                progress: {
                    color: '#007AFF'}
            };//全局监听标题栏按钮uni.listenTitleButton = function(thid) {
                let webView = thid.$mp.page.$getAppWebview();//webView加载完成时触发,开始监听子对象的onloaded事件webView.onloaded = function() {
                    let wv = webView.children()[0];//webView的子对象加载完成时触发  wv.onloaded = function() {
                        let url = wv.getURL();                        
                          //判断是否显示返回按钮if (
                            url.indexOf("hybrid/html/error.html") >= 0 ||url.indexOf("/index/index") >= 0 ||url.indexOf("/login/index") >= 0) {// console.log("标题栏隐藏返回按钮");webView.setTitleNViewButtonStyle(0, {
                                type: 'none'});
                            thid.backFun = function(object){}
                        } else {// console.log("标题栏显示返回按钮");webView.setTitleNViewButtonStyle(0, {
                                type: 'back'});
                            thid.backFun = function(object){if(object.index == 0){//回退                                    uni.navigateBack();
                                }
                            }
                        }                        //因为我们手动设置了一些属性,导致标题栏的title不能自动获取、设置,这里需要我们手动设置一下                        uni.setNavigationBarTitle({
                            title: wv.getTitle()
                        });
                    }
                }//webView手动加载、便于触发方法                webView.loadURL(thid.url);
            }
        },
        onShow: function() {

        },
        onHide: function() {

        }
    }</script>

<style>/*每个页面公共css */</style>

View Code

 

  index.vue

<!-- vue单文件组件 -->
<template>
    <!-- 注意必须有一个view,且只能有一个根view。所有内容写在这个view下面 -->
    <view class="main">
        <!-- 直接嵌入页面 -->
        <web-view id="webView" :src="url" :webview-styles="webviewStyles"></web-view>
    </view>
</template>

<!-- js代码,es6语法 -->
<script>//外部文件导入import * as util from '../../common/js/util.js';

    export default {
        data() {return {//当前webview请求的urlurl: uni.phoneServiceAddress + "/index/index",//进度条颜色样式                webviewStyles: uni.webviewStyles,//回退按钮事件,比如第一页是不需要回退按钮,点进去之后的页面才需要backFun:function(object){}
            }
        },//点击标题栏按钮,这里主要是用于回退按钮onNavigationBarButtonTap:function(object){this.backFun(object);
        },//页面装载完成,开始监听webview路径变化onReady: function(options) {
            console.log("onReady");// #ifdef APP-PLUSuni.listenTitleButton(this);// #endif        },
        onLoad: function(options) {
            console.log("onLoad");
        },
        onShow: function(options) {
            console.log("onShow");
        },// 点击导航栏,webview重新请求this.urlonTabItemTap: function(object) {// #ifdef APP-PLUSlet wv = this.$mp.page.$getAppWebview().children()[0]; 
            wv.loadURL(this.url);// #endif        }
    }</script>

<!-- css样式代码 -->
<style>/* css外部文件导入 */@import "../../common/css/uni.css";</style>

View Code

 

  然后其他的页面跟首页差不多,只是this.url的路径不同,同时,如果标题栏还需要其他按钮(比如右边再来个分享、或者添加按钮),就再加一个按钮,然后操作不同的下标

 

  配置错误页面

 

 

   webview组件

  webview组件介绍:https://uniapp.dcloud.io/component/web-view

 

  webview网页与App的交互

 

  1、webview调用uni-app的api,那几个路径的跳转都没有问题,postMessage说是在特定时机(后退、分享等)中才会触发,但是我一次都没有成功

  需要注意:在webview网页中调uni-app的api或者是5+扩展规范,需要监听原生扩展的事件,等待plus ready

document.addEventListener('UniAppJSBridgeReady', function() {
        uni.navigateTo({
            url: 'page/index/index'});
});

  或者使用mui已经帮我们封装好了方法,所有的5+规范的api都可以调

mui.plusReady(function() {
    plus.nativeUI.toast("xxxxxxx");
});

 

  2、uni-app调用webview网页的方法,可以直接在uni-app的代码里面使用5+规范中的webview对象的evaljs方法,将js代码发生到webview页面去执行,

  api地址:http://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.evalJS,例如

plus.webview.currentWebview()[0].evalJS("alert('哈哈哈')");

  webview页面就会弹出"哈哈哈"弹窗

 

  但有一点要注意,比如在webview页面使用5+规范去操作uni-app原生标题栏按钮的回调事件中,我们发现,在回调方法的作用域可以访问到外面的对象,也可以是获取到dom文档里的标签、元素,但直接修改DOM文档发现时不起作用的,看文档才发现,原来webview的层级比里面的内容要高,这时候我们选择下面这样方案

mui.plusReady(function () {
    let webView = plus.webview.currentWebview();//webView加载完成时触发,开始监听子对象的onloaded事件webView.onloaded = function() {
        let wv = webView.children()[0];//webView的子对象加载完成时触发wv.onloaded = function () {/* 标题栏按钮 */webView.setTitleNViewButtonStyle(1, {
                onclick: function (event) {// 将JS脚本发送到Webview窗口中运行,可用于实现Webview窗口间的数据通讯wv.evalJS("show()");
                }
            });
        }
    }
});function show() {
    
}

 

 

  mui部分

  项目工程结构就是我们之前熟悉的springboot + thymeleaf + springdata-jpa,开发起来除了页面风格(移动端)不同,其他的都还好

 

  mui部分主要是业务页面、功能的开发,有时候也需要调用5+规范的api,比如调用手机相机、文件管理、系统通知等,需要用到的时候就看api:http://www.html5plus.org/doc/h5p.html

  页面开发主要就参考mui的新手文档(https://dev.dcloud.net.cn/mui/getting-started/)、官网演示(https://www.dcloud.io/mui.html)、文档(https://dev.dcloud.net.cn/mui/ui/)等,同时也参考别人的App页面设计(QQ、微信、支付宝、京东等)

  

  mui封装弹窗

  比如类似京东他们的这种弹窗,我认为比较好看,比较具有通用性

  

 

 

  所以也基于mui封装了自己的一套弹窗效果

  先看下演示

  

  代码

  css

  封装在common.css中

/* 封装自定义弹窗 上右下左,居中 */.huanzi-dialog {position: fixed;background-color: white;z-index: -1;overflow: hidden;
}.huanzi-dialog-top {width: 100%;top: -100%;border-radius: 0 0 13px 13px;
}.huanzi-dialog-right {width: 85%;top: 0;right: -85%;bottom: 0;border-radius: 13px 0 0 13px;
}.huanzi-dialog-bottom {width: 100%;bottom: -100%;border-radius: 13px 13px 0 0;
}.huanzi-dialog-left {width: 85%;top: 0;left: -85%;bottom: 0;border-radius: 0 13px 13px 0;
}.huanzi-dialog-center {border-radius: 13px;opacity: 0;/* 方案一 *//*margin: auto;
    left: 0;
    right: 0;
    bottom: 0;
    top: 0;*//* 方案二 */top: 50%;left: 50%;transform: translate3d(-50%, -50%, 0) scale(1.185);
}

View Code

 

  js

  封装在common.js中

/* 封装天讯弹窗 */var HuanziDialog = {
    mask: null,//mui遮阴层对象showSpeed: 300,//弹出速度hideSpeed: 100,//隐藏速度removeFlag: true,//close内部是否执行操作/**
     * 隐藏弹窗,内部方法
     * @param select jq元素选择器,#xxx、.xxx等,如果为空,则隐藏所有
     * @param callback 回调方法
     * @param speed 速度     */hideFun: function (select, callback, speed) {
        let $huanziDialog = select ? $(select) : $(".huanzi-dialog");
        speed = speed ? speed : HuanziDialog.hideSpeed;//上右下左,居中$huanziDialog.each(function () {
            let dialog = $(this);
            let clazz = dialog.attr("class");if (clazz.indexOf("huanzi-dialog-top") > -1) {
                dialog.animate({top: '-100%'}, speed);
            } else if (clazz.indexOf("huanzi-dialog-right") > -1) {
                dialog.animate({right: '-85%'}, speed);
            } else if (clazz.indexOf("huanzi-dialog-bottom") > -1) {
                dialog.animate({bottom: '-100%'}, speed);
            } else if (clazz.indexOf("huanzi-dialog-left") > -1) {
                dialog.animate({left: '-85%'}, speed);
            } else if (clazz.indexOf("huanzi-dialog-center") > -1) {
                dialog.animate({opacity: 0}, speed);
            }
            setTimeout(function () {
                dialog.css("z-index", "-1");
            }, speed)
        });

        callback && callback();
    },/**
     * 显示弹窗,内部方法
     * @param select jq元素选择器,#xxx、.xxx等,如果为空,则显示所有
     * @param callback 回调方法
     * @param speed 速度     */showFun: function (select, callback, speed) {
        let $huanziDialog = select ? $(select) : $(".huanzi-dialog");
        speed = speed ? speed : HuanziDialog.hideSpeed;//上右下左,居中$huanziDialog.each(function () {
            let dialog = $(this);
            dialog.css("z-index", "999");

            let clazz = dialog.attr("class");if (clazz.indexOf("huanzi-dialog-top") > -1) {
                dialog.animate({top: '0%'}, speed);
            } else if (clazz.indexOf("huanzi-dialog-right") > -1) {
                dialog.animate({right: '0%'}, speed);
            } else if (clazz.indexOf("huanzi-dialog-bottom") > -1) {
                dialog.animate({bottom: '0%'}, speed);
            } else if (clazz.indexOf("huanzi-dialog-left") > -1) {
                dialog.animate({left: '0%'}, speed);
            } else if (clazz.indexOf("huanzi-dialog-center") > -1) {
                dialog.animate({opacity: 1}, speed);
            }
        });
        HuanziDialog.removeFlag = true;
        callback && callback();
    },/**
     * 初始化mui遮阴层对象     */init: function () {
        HuanziDialog.mask = mui.createMask();/**
         * 重写close方法         */HuanziDialog.mask.close = function () {if (!HuanziDialog.removeFlag) {return;
            }//方法直接在这里执行            HuanziDialog.hideFun();//调用删除            HuanziDialog.mask._remove();
        };
    },/**
     * 显示弹窗,供外部调用(参数同内部方法一致)     */show: function (select, callback, speed) {
        HuanziDialog.showFun(select, callback, speed);
        HuanziDialog.mask.show();//显示遮罩    },/**
     * 隐藏弹窗,供外部调用(参数同内部方法一致)     */hide: function (select, callback, speed) {
        HuanziDialog.hideFun(select, callback, speed);
        HuanziDialog.mask.close();//关闭遮罩    },/**
     * 警告框
     * @param title 标题
     * @param message 内容
     * @param callback 点击确认的回调     */alert: function (title, message, callback) {
        let $html = $("<div class="mui-popup mui-popup-in" style="display: block;">" +
            "<div class="mui-popup-inner">" +
            "   <div class="mui-popup-title">" + title + "</div>" +
            "   <div class="mui-popup-text">" + message + "</div>" +
            "</div>" +
            "<div class="mui-popup-buttons">" +
            "<span class="mui-popup-button mui-popup-button-bold confirm-but">确定</span>" +
            "</div>" +
            "</div>");
        $html.find(".confirm-but").click(function () {
            HuanziDialog.removeFlag = true;
            HuanziDialog.mask.close();
            $html.remove();
            callback && callback();
        });
        HuanziDialog.mask.show();//显示遮罩HuanziDialog.removeFlag = false;
        $("body").append($html);
    },/**
     * 确认消息框
     * @param title 标题
     * @param message 内容
     * @param callback 点击确认的回调     */confirm: function (title, message, callback) {
        let $html = $("<div class="mui-popup mui-popup-in" style="display: block;">" +
            "<div class="mui-popup-inner">" +
            "   <div class="mui-popup-title">" + title + "</div>" +
            "   <div class="mui-popup-text">" + message + "</div>" +
            "</div>" +
            "<div class="mui-popup-buttons">" +
            "<span class="mui-popup-button mui-popup-button-bold cancel-but" style='color: #585858;'>取消</span>" +
            "<span class="mui-popup-button mui-popup-button-bold confirm-but">确定</span>" +
            "</div>" +
            "</div>");
        $html.find(".cancel-but").click(function () {
            HuanziDialog.removeFlag = true;
            HuanziDialog.mask.close();
            $html.remove();
        });
        $html.find(".confirm-but").click(function () {
            $html.find(".cancel-but").click();
            callback && callback();
        });

        HuanziDialog.mask.show();//显示遮罩HuanziDialog.removeFlag = false;
        $("body").append($html);
    },/**
     * 自动消失提示弹窗
     * @param message 内容
     * @param speed 存在时间     */toast: function (message, speed) {
        speed = speed ? speed : 2000;
        let $html = $("<div class="huanzi-dialog huanzi-dialog-center" style="width: 45%;height: 20%;opacity: 1;z-index: 999;background-color: #5a5a5ad1;">" +
            "    <p style=" position: relative; top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0) scale(1); color: #e0e0e0; font-size: 20px; ">" + message + "</p>" +
            "</div>");
        $("body").append($html);
        setTimeout(function () {
            $html.remove();
        }, speed);
    }
};//先初始化自定义弹窗HuanziDialog.init();

View Code

 

  html

  测试页面

<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>基于MUI封装常用弹窗</title><!-- jquery --> <script th:src="@{/webjars/jquery/3.1.1/jquery.min.js}"></script><!-- 引入mui框架 --><link rel='stylesheet' th:href="@{/common/mui/css/mui.css}"/><script th:src="@{/common/mui/js/mui.js}"></script><!-- 最后引入公用代码 --><link rel='stylesheet' th:href="@{/common/common.css}"/><script th:src="@{/common/common.js}"></script><style>body{text-align: center;}.mui-btn{width: 50%;margin: 10px auto;}</style></head><body><h4>基于MUI封装常用弹窗</h4><button class="mui-btn" onclick="HuanziDialog.show('#top')">上</button><button class="mui-btn"  onclick="HuanziDialog.show('#bottom')">下</button><button class="mui-btn"  onclick="HuanziDialog.show('#left')">左</button><button class="mui-btn"  onclick="HuanziDialog.show('#right')">右</button><button class="mui-btn"  onclick="HuanziDialog.show('#center')">居中</button><button class="mui-btn"  onclick="HuanziDialog.alert('系统提示','我是警告框!',function() {console.log('你已确认警告!')})">警告框</button><button class="mui-btn"  onclick="HuanziDialog.confirm('系统提示','确认要XXX吗?',function() {HuanziDialog.toast('很好,你点击了确认!');console.log('很好,你点击了确认!')})">确认框</button><button class="mui-btn"  onclick="HuanziDialog.toast('提交成功')">自动消失提示框</button><!-- 上 --><div id="top" class="huanzi-dialog huanzi-dialog-top" style="height: 500px"><h5>我从上边弹出</h5></div><!-- 下 --><div id="bottom" class="huanzi-dialog huanzi-dialog-bottom" style="height: 500px"><h5>我从下边弹出</h5></div><!-- 左 --><div id="left" class="huanzi-dialog huanzi-dialog-left"><h5>我从左边弹出</h5></div><!-- 右 --><div id="right" class="huanzi-dialog huanzi-dialog-right"><h5>我从右边弹出</h5></div><!-- 居中 --><div id="center" class="huanzi-dialog huanzi-dialog-center" style="width: 65%;height: 30%"><h5>我从中间弹出</h5></div></body></html>

View Code

 

  其实后面的警告框、确认框的样式就是mui的5+端样式,那我们为什么还要封装呢?在开发中我们发现,在PS端浏览器将调试模式改成手机端,mui的封装的弹窗是上面的效果,但到真机上运行它又变成原生的弹窗样式,原来mui底层有进行了判断,安卓、苹果、5+等样式都不一样,这里我们为了弹窗风格的统一,同时也是为了方便后期的统一调整,因此再进行了一层封装

 

  App调试、打包

  运行 -> 运行到手机或模拟器

  需要安装个模拟器(我的是雷电)、或者直接用USB数据先连接进行调试(PS:我的模拟器连接经常会断开,不知道是什么回事,有时候调试调试着就断开了,检查了也没有其他应用占用adb)

 

  App打包是在:发行 - > 原生App-云打包

  开发阶段,使用Dcloud公司的公用证书云打包就可以了,正式上线就需要自己的证书去打包

 

 

   打包成功后控制台就会返回下载链接

 

 

 

  后记

  移动端App uni-app + mui 开发暂时先记录到这,后续再补充;由于是公司的App,就不方便演示,等有空了再做个demo把完整的一套东西再做完整演示;

 

 

 

0% (0)
0% (0)