1. 程式人生 > >移動端App uni-app + mui 開發記錄

移動端App uni-app + mui 開發記錄

  前言

  uni-app

  uni-app是DCloud推出的終極跨平臺解決方案,是一個使用Vue.js開發所有前端應用的框架,官網:https://uniapp.dcloud.io/

 

  mui

  號稱最接近原生APP體驗的高效能前端框架,官網:https://dev.dcloud.net.cn/mui/

  個人覺得,mui除了頁面設計很接近原生App之外,還有一個特點就是能方便的使用App擴充套件規範Html5 Plus(http://www.html5plus.org/doc/h5p.html),我們能在它的原始碼中看到比較多的地方都有使用到

 

 

 

  開發工具

  使用HBuilderX開發工具寫uni-app的程式碼,以及打包App等工作,主要的業務功能依舊是使用我們熟悉的idea開發,不過頁面從webPC端風格改成了移動端風格

 

 

  整體我們採用uni-app + mui的方式,使用的是官方推薦的uni-app原生標題欄跟導航欄+嵌入webview遠端服務的頁面,也就是說除了頭部、尾部,中間的內容都是類似iframe嵌入進去

 

 

   

  為方便以後查閱,特此記錄

 

  uni-app部分

  我在App.vue中對uni物件進行全域性賦值,這樣在每個頁面都呼叫到,這樣做的目的是為了方便全域性修改

 

  設定進度條顏色、監聽webview的url變化判斷是否需要導航欄按鈕等操作

  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

 

  App.vue

<script>
    export default {
        onLaunch: function() {
            //應用載入後初始後端服務地址
            uni.phoneServiceAddress = "http://qch2.vipgz2.idcfengye.com"; //為了方便App演示,這裡開了一個內網穿透
            
            //監聽軟鍵盤高度變化,隱藏或顯示tabbar
            uni.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請求的url
                url: 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-PLUS
            uni.listenTitleButton(this);
            // #endif
        },
        onLoad: function(options) {
            console.log("onLoad");
        },
        onShow: function(options) {
            console.log("onShow");
        },
        // 點選導航欄,webview重新請求this.url
        onTabItemTap: function(object) {
            // #ifdef APP-PLUS
            let 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元件介紹:https://uniapp.dcloud.io/component/web-view

 

  webview網頁與App的互動

  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");
});

  但有一點要注意,比如在操作標題欄按鈕的回撥事件中,我們直接去修改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部分

  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頁面設計  

  專案工程結構就是我們之前熟悉的springboot + thymeleaf + springdata-jpa,開發起來除了頁面風格(移動端)不同,其他的都還好

  

  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

 

  App除錯、打包

  執行 -> 執行到手機或模擬器

  需要安裝個模擬器(我的是雷電)、或者直接用USB資料先連線進行除錯(PS:我的模擬器連線經常會斷開,不知道是什麼回事,有時候除錯除錯著就斷開了,檢查了也沒有其他應用佔用adb)

 

  App打包是在:發行 - > 原生App-雲打包

  開發階段,使用Dcloud公司的公用證書雲打包就可以了,正式上線就需要自己的證書去打包

   打包成功後控制檯就會返回下載連結

 

 

 

  後記

  移動端開發暫時先記錄到這,後續再補充;由於是公司的App,就不方便演示,等有空了再做個demo把完整的一套東西再做完整演示;

 

&n