移動端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" } }
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