前言

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

  

  监听标题栏按钮

  设置进度条颜色

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

  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>

  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>

  然后其他的页面跟首页差不多,只是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封装了自己的一套弹窗效果

  先看下演示

  

  代码

  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:;
right: -85%;
bottom:;
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:;
left: -85%;
bottom:;
border-radius: 0 13px 13px 0;
} .huanzi-dialog-center {
border-radius: 13px;
opacity:;
/* 方案一 */
/*margin: auto;
left: 0;
right: 0;
bottom: 0;
top: 0;*/ /* 方案二 */
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0) scale(1.185);
}

  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();

  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>

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

  封装头部尾部

  这里的封装其实就是文末补充的另一种方案,基于mui的标题栏、底部导航栏,进行简单封装

  common.css

/* 自定义头部,系统状态栏的高度暂时写死30px */
.huanzi-header{
position: fixed;
top:;
right:;
left:;
background-image: linear-gradient(to bottom right, #0061ff, #6aa2ff);
box-shadow: 0 1px 6px #ccc;
height: 74px;
} .huanzi-header .statusbar {
height: 30px;
width: 100%;
} .huanzi-header .titlebar{
padding-right: 10px;
padding-left: 10px;
border-bottom:;
} .huanzi-header .titlebar a {
margin: 15px 5px;
} .huanzi-header .titlebar * {
color: white;
} .huanzi-header .mui-title{
line-height: 55px !important;
right: 100px;
left: 100px;
display: inline-block;
overflow: hidden;
width: auto;
margin:;
text-overflow: ellipsis;
} .huanzi-content {
position: absolute;
top: 74px;
bottom: 50px;
} /* 自定义页脚(底部导航栏) */
.huanzi-footer{
position: fixed;
right:;
left:;
bottom:;
background-color: white;
box-shadow: 0 1px 6px #ccc;
height: 50px;
padding: 5px;
} .huanzi-footer .huanzi-footer-buttom{
height: 50px;
float: left;
color: black;
/* 宽度为:100/按钮个数 */
width: 25%;
} .huanzi-footer .huanzi-footer-buttom > p{
color: black;
} .huanzi-footer .select{
color: #0091fb;
} .huanzi-footer .select > p{
color: #0091fb;
}

  common.js

//底部按钮点击切换颜色
$(document).on("click",".huanzi-footer-buttom", function (e) {
$(".huanzi-footer-buttom").each(function () {
$(this).removeClass("select");
});
$(this).addClass("select");
});

  自定义弹窗例子

  需要在head.html中引入jquery、mui、common的js、css

<!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>
<script th:replace="common/head::static"></script>
<style>
body{
text-align: center;
} .mui-btn{
width: 50%;
margin: 10px auto;
}
</style>
</head>
<body>
<!-- 头部 -->
<header class="huanzi-header">
<div class="statusbar"></div>
<div class="titlebar">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">基于MUI封装常用弹窗</h1>
<a class="mui-icon mui-icon-bars mui-pull-right"></a>
</div>
</header> <!-- 内容(可滑动区域) -->
<div class="huanzi-content mui-scroll-wrapper">
<div class=" mui-scroll">
<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> <button class="mui-btn">无用按钮</button>
<button class="mui-btn">无用按钮</button>
<button class="mui-btn">无用按钮</button>
<button class="mui-btn">无用按钮</button>
<button class="mui-btn">无用按钮</button>
<button class="mui-btn">无用按钮</button>
<button class="mui-btn">无用按钮</button>
</div>
</div>
<!-- 例如弹窗等内容,需要放在外面 -->
<div>
<!-- 上 -->
<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> </div> <!-- 底部导航栏 -->
<footer class="huanzi-footer">
<div class="huanzi-footer-buttom select">
<i class="mui-icon mui-icon-phone"></i>
<p>电话</p>
</div>
<div class="huanzi-footer-buttom">
<i class="mui-icon mui-icon-email"></i>
<p>邮件</p>
</div>
<div class="huanzi-footer-buttom">
<i class="mui-icon mui-icon-chatbubble"></i>
<p>短信</p>
</div>
<div class="huanzi-footer-buttom">
<i class="mui-icon mui-icon-weixin"></i>
<p>微信</p>
</div>
</footer>
</body>
</html>

  效果演示

  2020-03-04更新

  问题:按照前面的想法,我们每个页面都要加入头部、尾部,但这样跳转页面时会造成“白屏”的情况,严重影响浏览效果

  解决办法:我们创建一个main主页面,只有主页面有头部、尾部,中间内容嵌入iframe内容子页面(子页面正常html页面),如果在当前页面进行跳转操作,也是在iframe中进行跳转,而如果点击尾部按钮切换模块、页面,那就切换iframe标签的src进行更新url,这样我们在跳转页面时,头部、尾部都不会刷新,浏览效果更佳,而且还可以减少重复代码

  common.js

  其他的都不变,尾部按钮点击事件需要修改一下,同时加入iframe标签的load事件处理

//省略其他内容

//底部按钮点击事件
$(document).on("click", ".huanzi-footer-buttom", function (e) {
//iframe跳转新页面
$("#mainIframe")[0].src = ctx + $(this).data("url"); //切换颜色
$(".huanzi-footer-buttom").each(function () {
$(this).removeClass("select");
});
$(this).addClass("select");
}); //mainIframe onload事件
function mainIframeLoadFun(mainIframe) {
//自适应高度
mainIframe.height = $('.huanzi-content')[0].scrollHeight; //修改标题 //子页面与父页面同源获取方法
// let title = document.getElementById('mainIframe').contentWindow.document.title;//iframe中子页面的title
let $mainFrame=$('#mainIframe');
let title = $mainFrame.contents().attr("title"); $("title").text(title);
$(".mui-title").text(title);
}

  main.html

  主页面,主要分为头部、中间内容、尾部,中间内容改成iframe标签,在onload事件中进行高度自适应

<!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></title>
<script th:replace="common/head::static"></script>
<style>
body{
text-align: center;
}
</style>
</head>
<body>
<!-- 头部 -->
<header class="huanzi-header">
<div class="statusbar"></div>
<div class="titlebar">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title"></h1>
<a class="mui-icon mui-icon-bars mui-pull-right"></a>
</div>
</header> <!-- 内容(可滑动区域) -->
<div class="huanzi-content mui-scroll-wrapper">
<div class=" mui-scroll">
<!-- 直接嵌入iframe,且自适应宽高 -->
<iframe id="mainIframe" src="/test1" width="100%" onload="mainIframeLoadFun(this)"></iframe>
</div>
</div> <!-- 底部导航栏 -->
<footer class="huanzi-footer">
<div class="huanzi-footer-buttom select" data-url="/test1">
<i class="mui-icon mui-icon-phone"></i>
<p>页面1</p>
</div>
<div class="huanzi-footer-buttom" data-url="/test2">
<i class="mui-icon mui-icon-email"></i>
<p>页面2</p>
</div>
<div class="huanzi-footer-buttom" data-url="/test3">
<i class="mui-icon mui-icon-chatbubble"></i>
<p>页面3</p>
</div>
<div class="huanzi-footer-buttom" data-url="/test4">
<i class="mui-icon mui-icon-weixin"></i>
<p>页面4</p>
</div>
</footer>
</body>
</html>

  test1.html - test5.html(这几个页面内容都差不多,贴出一个就可以了,不同的是里面的值,还有就是test4.html页面里面有个跳转test5.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>页面4</title>
<script th:replace="common/head::static"></script>
<style>
body{
text-align: center;
}
</style>
</head>
<body>
<button class="mui-btn" onclick="window.location.href = ctx + '/test5'">跳转页面5</button>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
<h1>页面4</h1>
</body>
</html>

  controller

  控制器控制页面跳转(代码几乎一模一样,我就只贴一个就好了)

    //跳转主页面
@GetMapping("main")
public ModelAndView main() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("main");
return modelAndView;
}

  

  效果演示

  

  App调试、打包

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

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

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

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

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

  后记

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

  另一种方案

  虽然官方推荐尽量使用原生导航。甚至有时需要牺牲一些不是很重要的需求。但有时候我们就是想自定义原生标题栏,特别是我们是webview嵌入的方式

    "globalStyle": {
//隐藏原生标题栏,主意事项请查阅官网:https://uniapp.dcloud.io/collocation/pages?id=customnav
"navigationStyle":"custom"
},

  如果要自定义导航栏,有哪些主要的点,官方在这里已经说得很清楚了:https://uniapp.dcloud.io/collocation/pages?id=customnav,但如果我们采用的是webview嵌入的方式,就要注意了,<web-view> 组件默认铺满全屏并且层级高于前端组件,如果我们按照文档中操作,发现还是会顶到系统状态栏

   因此占高div我们最好也写在webview里面,系统状态栏的高度可以动态获取:http://www.html5plus.org/doc/zh_cn/navigator.html#plus.navigator.getStatusbarHeight

mui.plusReady(function(){
//获取系统状态栏的高度,单位为像素(px),值为Webview中的逻辑高度单位
let statusbarHeight = plus.navigator.getStatusbarHeight();
alert(statusbarHeight)
});

  自己写标题栏的话可以直接用mui的这个,或者基于它,我们自己再封装一个自己的标题栏

  

  但是这样对代码的书写规范有一定的要求,页面统一分为头部、内容、尾部,中间的内容是可滑动区域,例如:

<body>
<!-- 头部 -->
<header id="header" class="mui-bar mui-bar-nav">
<!-- 系统状态栏占高div -->
<div></div> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">头部导航栏</h1>
<a class="mui-icon mui-icon-bars mui-pull-right"></a>
</header> <!-- 内容(可滑动区域) -->
<div class="mui-scroll-wrapper">
<div class=" mui-scroll">
<p>这里是内容</p>
</div>
</div> <!-- 底部(如有需要,可扩展尾部导航栏) -->
</body>

  当然,我们可以进行统一封装,使用thymeleaf的替换,或者使用js去追加,这样可以减少每个页面的代码量,方便维护,但是视觉上就会有闪烁效果,因为每个页面的头部可能不一样,需要用js去追加,这个就需要权衡利弊选择合适的方式

  注:封装代码在前面mui封装部分

  补充

  2020-02-25补充:自定义tabbar + webview解决方案

  uniapp原生头尾+webview组合,底部的TabBar按钮需要根据登录角色的权限来动态控制数量,但目前官方并不支持动态修改TabBar隐藏或显示某一项,因此我们选用uniapp自定义TabBar实现(用的是这个插件:自定义动态TabBar;图片上传七牛云、阿里OSS;),同时配合Storage模块(http://www.html5plus.org/doc/zh_cn/storage.html)在webview页面进行存储登录角色权限,登录成功后跳转uniapp固定页面,进行读取判断动态控制tabbar

  但webview组件默认全屏显示,会覆盖底部的tabbar按钮,而且webview组件的webview-styles并不支持设置高度,需要使用APP扩展插件5+plus来控制(http://www.html5plus.org/doc/zh_cn/webview.html),但当我们调用setStyle设置百分比高度发现并没有生效,原因不明,很奇怪

  无奈,只能用5+plus动态创建webview组件,创建时传入style样式控制高度,这样就解决自定义tabbar按钮被覆盖的问题

            //动态创建,控制高度
var w=plus.webview.create(this.url,'index',{height:'93%'});
w.show();

  代码开源

  代码已经开源、托管到我的GitHub、码云:

  GitHub:https://github.com/huanzi-qch/springBoot

  码云:https://gitee.com/huanzi-qch/springBoot

移动端App uni-app + mui 开发记录的更多相关文章

  1. MUI开发记录

    最近很久没有更新博客了,因为一直在学习前端h5 手机app的开发.曾经一度觉得自己css和js学得不错,进入到前端领域后才发现水很深~ HUuilder使用安卓模拟器 安卓模拟器有很多,我这里以夜神模 ...

  2. MUI开发记录——我的考勤

    已经好久没有更新技术博客了,因为最近一直在做跨平台web app应用的开发,由于是刚做这个,也没太多经验同大家分享,可我是一个闲不住的人,我还是决定于百忙之中抽空整理一篇,记录下开发历程......— ...

  3. Anytime项目开发记录0

    Anytime,中文名:我很忙. 开发者:孤独的猫咪神. 这个项目会持续更新,直到我决定不再维护这个APP. 2014年3月10日:近日有事,暂时断更.希望可以会尽快完事. 2014年3月27日:很抱 ...

  4. python 全栈开发,Day127(app端内容播放,web端的玩具,app通过websocket远程遥控玩具播放内容,玩具管理页面)

    昨日内容回顾 1. 小爬爬 内容采集 XMLY 的 儿童频道 requests 2. 登陆 注册 自动登陆 退出 mui.post("请求地址",{数据},function(){} ...

  5. mui开发app之html5+,5+Runtime,5+sdk,native.js

    说说几个名词 html5:目前最新的html规范,w3c联盟制定,手机端主要由webkit实现规范,对用户来说就是浏览器实现了它 html5+:所谓"+",扩充了html5原本没有 ...

  6. mui开发app前言(一)

    dcloud mui开发app前言 大一那会就听说html5快要发布了,前景无量,厉害到能写操作系统==|||(什么???蛤?) 似乎html5标准还没正式发布那会,使用hybrid模式开发app已经 ...

  7. 跨平台移动APP开发进阶(二)HTML5+、mui开发移动app教程

    前端开发APP,从HBuilder开始~ 序 通过 HTML5 开发移动App 时,会发现HTML5 很多能力不具备.为弥补HTML5 能力的不足,在W3C 中国的指导下成立了www.HTML5Plu ...

  8. MUI开发APP,scroll组件,运用到区域滚动

    最近在开发APP的过程中,遇到一个问题,就是内容有一个固定的头部和底部.         头部就是我们常用的header了,底部的话,就放置一个button,用来提交页面数据或者进入下一个页面等,效果 ...

  9. mui开发app之js将base64转图片文件

    之前我已经做过一个利用cropper裁剪并且制作头像的功能.如何在mui app中实现相册或相机获取图片后裁剪做头像请看另一篇博客:mui开发app之cropper裁剪后上传头像的实现 但是当时裁剪后 ...

随机推荐

  1. unity 开启外部摄像头

    在unity中建立一个image作为摄像头显示画面,然后通过命令render到image上即可. public WebCamTexture webTex; public string deviceNa ...

  2. [2018-01-12] jquery获取当前元素的兄弟元素

    $('#id').siblings() 当前元素所有的兄弟节点$('#id').prev() 当前元素前一个兄弟节点$('#id').prevaAll() 当前元素之前所有的兄弟节点$('#id'). ...

  3. Ubuntu 18.04 下安装pip3及pygame模块

    1.Ubuntu下pip3的安装.升级.卸载 安装pip3 sudo apt-get install python3-pip 升级pip3 sudo pip3 install --upgrade pi ...

  4. __new__与__init__的区别和应用场景

    创建实例的时候, 先运行的_new_方法, _new_创建对象 Student object(实例)返回给 _init_ 里面的第一个参数self class Student(object): def ...

  5. 深度学习tensorflow实战笔记(1)全连接神经网络(FCN)训练自己的数据(从txt文件中读取)

    1.准备数据 把数据放进txt文件中(数据量大的话,就写一段程序自己把数据自动的写入txt文件中,任何语言都能实现),数据之间用逗号隔开,最后一列标注数据的标签(用于分类),比如0,1.每一行表示一个 ...

  6. CSPS模拟 67

    炸分炸的厉害.(当然这跟b哥定律无关 话说好久没人嘲笑我菜了,快飘的不知道到哪了. 谁能讽我两句我不要面子的. 另外在博客上写些没用的东西好浪费精力啊我又不想当网红 主要是考试的时候心态不稳. 以为T ...

  7. Ubuntu13.10编译android源码中遇到的问题

    1. jdk的版本不对 我开始安装的是最新的jdk7,但是编译时会出现jdk的版本

  8. js 重写a标签的href属性和onclick事件

    适应场景:假如移动端拨打电话,需要给a标签添加href属性,但是由于需求,需要链接跳转的同时给a标签添加onclick事件,如果不做任何处理的话,默认执行点击事件,而不会跳转href属性的链接. 怎么 ...

  9. 安装Windows和Ubuntu双系统--Ubuntu安装过程识别不了硬盘

    Linux识别不了固态硬盘 安装过程: 自己本身的是Windows 10,一块125g 固态 ,一块1T的机械硬盘. 通过rufus 制作ubuntu的启动盘 在BIOS中关闭电脑的安全启动选项,并且 ...

  10. introduce new products

    Today's the day. I'm giving you the heads up. Our company is rolling up its new line of cell phones. ...