近年来国内出现了一些可让前端人员编写移动端App的IDE,Hbuilder X是DCloud推出的一款免费开发工具,最大的亮点是能够开发App,利用html5+技术,结合mui+nativejs能够在云端打包,主要用到的技术就是HTML五、JS、CSS,一套代码,便可生成Android和IOS对应的两种App。最先的App开发只有原生这个概念,Html页面只是用来作一些简单的静态资源展现,可是随着H5的兴盛,你们发现不少功能、逻辑均可用web来实现,而后原生做为容器显示,并且H5展现的页面更炫酷、功能更丰富,在IOS、Andriod中都有很好的支持,这样开发效率更高、成本更低,同时用户体验也不错。javascript
项目已上传github,欢迎你们下载交流。css
前端项目地址:github.com/Hanxueqing/…html
在线项目手册:hanxueqing.github.io/Maoer-App-H…前端
UI框架:MUI(官方推荐的模拟原生App的UI框架)vue
JS框架:VUEhtml5
API:H5+、Native.js(原生40万API随意调用)java
编辑器:HBuilder,在5+ App项目下编写的HTML、js等文件,会被打包到原生的安装包(Android是apk包、iOS是ipa包)。ios
# 克隆到本地
git clone git@github.com:Hanxueqing/Maoer-App-HBuilder.git
# 放到HBuilder环境下运行
# 使用数据线链接手机
# IOS系统在AppStore下载HBuilder插件
# 在HBuilder中输入ctrl+r开启真机演示
复制代码
在官网地址选择合适的版本下载安装:git
www.dcloud.io/hbuilderx.h…github
打开HBuilder,在菜单栏中选择文件——新建——项目,选择5+App,建立一个mui项目,填写文件名称、保存位置,点击建立,会给你生成一个包含mui的js、css、字体资源的项目模版。
新建完成后,会在左侧的项目管理器中出现以下目录结构,跟咱们平时作前端开发的项目相似。mainifest.json文件中存储的是app相关的配置。
使用数据线链接手机和电脑,在Android设备会自动安装并启动HBuilder调试基座,IOS系统的同窗请下载一个名字叫HBuilder的调试插件,点击窗口上方的播放键小图标或者使用快捷键command+r在手机上运行。
真机运行有3个特色:
若是你真机失败,注意看控制台的提示,或点HBuilder菜单-运行里的故障排查指南。 注意:真机联调App时,提供的是一个测试环境,并不真实发生打包,调试基座App的名字、图标、启动封面图片、是否可旋转这些只有打包才能更改的属性不会由于开发者修改manifest文件而变化。只有修改manifest且点击菜单发行-打包后,上述4个设置才能更改。
运行后,HBuilder中修改页面代码,保存后会自动同步到手机中,若是手机当前展现着被修改的页面,则会刷新页面。尝试在js中在plus ready以后编写console.log,或者改写错误的js,能够直接在HBuilder的控制台看到结果。若是真机运行遇到各类故障,请点击运行菜单里的真机运行常见故障指南。
mui框架将不少功能配置都集中在mui.init方法中,要使用某项功能,只须要在mui.init方法中完成对应参数配置便可,目前支持在mui.init方法中配置的功能包括:建立子页面、关闭页面、手势事件配置、预加载、下拉刷新、上拉加载、设置系统状态栏背景颜色。
//mui初始化
mui.init();
复制代码
编写三个tab选项:首页、好玩、设置,在href中填写展现页面的id。
<nav class="mui-bar mui-bar-tab">
<!-- href写id -->
<a id="defaultTab" class="mui-tab-item mui-active" href="home.html">
<span class="mui-icon mui-icon-home"></span>
<span class="mui-tab-label">首页</span>
</a>
</a>
<a class="mui-tab-item" href="play.html">
<span class="mui-icon mui-icon-paperplane"></span>
<span class="mui-tab-label">好玩</span>
</a>
<a class="mui-tab-item" href="mine.html">
<span class="mui-icon mui-icon-gear"></span>
<span class="mui-tab-label">设置</span>
</a>
</nav>
复制代码
先经过var self = plus.webview.currentWebview();
建立一个主窗口self,而后内部经过循环拿到三个子窗口,经过H5+方法 Webview——create建立新的Webview窗口,判断i是否大于0来判断当前窗口是不是第二、3窗口,若是是则隐藏,若是不是则说明为第一个子窗口,就追加到self主窗口中,而且经过subpage_style样式规定它在主窗口的展现位置。
H5 + create方法
WebviewObject plus.webview.create( url, id, styles, extras );
www.html5plus.org/doc/zh_cn/w…
<script type="text/javascript" charset="utf-8">
//mui初始化
mui.init();
// var subpages = ['tab-webview-subpage-about.html', 'tab-webview-subpage-chat.html', 'tab-webview-subpage-contact.html', 'tab-webview-subpage-setting.html'];
//配置子页面
var subpages = [
{url:"./pages/home/",id:"home.html"},
{url:"./pages/play/",id:"play.html"},
{url:"./pages/mine/",id:"mine.html"},
]
var subpage_style = {//规定子窗口在主窗口中的位置
top: '0px',
bottom: '51px'
};
var aniShow = {}; //建立一个空对象
//建立子页面,首个选项卡页面显示,其它均隐藏;
mui.plusReady(function() {//放到plusReady中才能够调用h5+的plus方法
var self = plus.webview.currentWebview();//主窗口的对象
for (var i = 0; i < 3; i++) {//循环三次
var temp = {};
// WebviewObject plus.webview.create( url, id, styles, extras );
var sub = plus.webview.create(subpages[i].url+subpages[i].id,subpages[i].id,subpage_style);
if (i > 0) { //第二个与第三个窗口隐藏
sub.hide();//调用hide方法
}else{
temp[subpages[i].id] = "true"; //{home.html:"true"}
mui.extend(aniShow,temp); //对象扩展 aniShow = {home.html:"true"}
}
self.append(sub);//子窗口添加到主窗口
}
});
复制代码
在app开发中,对于HTML5+应用的页面有一个很重要的“plusready”事件,此事件会在页面加载后自动触发,表示全部HTML5+ API可使用,在此事件触发以前不能调用HTML5+ API,若要使用HTML5+扩展api,必须等plusready事件发生后才能正常使用,因此应该在此事件回调函数中调用页面初始化须要调用的HTML5+ API,而不该该在onload或DOMContentLoaded事件中调用。mui将该事件封装成了mui.plusReady()
方法,涉及到HTML5+的api,建议都写在mui.plusReady方法中。以下为打印当前页面URL的示例:
mui.plusReady(function(){
console.log("当前页面URL:"+plus.webview.currentWebview().getURL());
});
复制代码
若是手机版本是ios10+系统,即便不写plusready,内部也能够拿到plus对象,若是是安卓系统,系统报错plus is not defined说明找不到plus对象,须要将方法写在plusready中。
经过subpages[0].id
获取当前激活选项,经过事件委托,给全部的a标签动态绑定事件,让后续动态添加的元素也有以前的事件。
//当前激活选项
var activeTab = subpages[0].id; //"home.html"
//选项卡点击事件
//事件委托 让后续动态添加的元素也有以前的事件
mui('.mui-bar-tab').on('tap', 'a', function(e) { //给全部的a标签动态绑定事件
var targetTab = this.getAttribute('href'); //得到href属性 "home.html"
if (targetTab == activeTab) { //若是href属性和id相同
return;
}
复制代码
dev.dcloud.net.cn/mui/util/#o…
咱们常常会有经过navigator.userAgent
判断当前运行环境的需求,mui对此进行了封装,经过调用mui.os.XXX便可。
若是是ios操做系统直接打开对应页面,若是是非ios系统而且第一次进入该页面,则以fade-in动画的形式打开。
//显示目标选项卡
//若为iOS平台或非首次显示,则直接显示
//判断平台
//若是是ios操做系统直接打开对应页面 若是是非ios系统而且第一次进入该页面 则以动画的形式打开
if(mui.os.ios||aniShow[targetTab]){
plus.webview.show(targetTab);
}else{//若是是其余平台则以动画的形式打开
//不然,使用fade-in动画,且保存变量
var temp = {};
temp[targetTab] = "true"; //temp = [“play.html”:"true"]
mui.extend(aniShow,temp);//aniShow = ["home.html":"true","play.html":"true"]
plus.webview.show(targetTab,"fade-in",300);
}
复制代码
**请注意,mui只封装了部分HTML5Plus Api,学会mui框架不表明能够不学习HTML5Plus规范。mui不会作的很重,只是颇有限的经过封装简化了常见开发过程。 **
打开对应页面以后须要将以前激活的页面隐藏,而后将activeTab更改成当前的targetTab。
//隐藏当前;
plus.webview.hide(activeTab);
//更改当前活跃的选项卡
activeTab = targetTab;
复制代码
最后经过自定义事件,模拟点击"首页选项卡",实现当前点击的选项卡高亮显示。
//自定义事件,模拟点击“首页选项卡”
document.addEventListener('gohome', function() {
var defaultTab = document.getElementById("defaultTab");
//模拟首页点击
mui.trigger(defaultTab, 'tap');
//切换选项卡高亮
var current = document.querySelector(".mui-bar-tab>.mui-tab-item.mui-active");
if (defaultTab !== current) {
current.classList.remove('mui-active');
defaultTab.classList.add('mui-active');
}
});
复制代码
上bootcdn将swiper的样式和js文件复制到本地
此项目咱们要使用vue框架进行开发,因此将vue.js也复制到本地。
封装rem.js文件,实现移动端响应式布局。
document.documentElement.style.fontSize =
document.documentElement.clientWidth / 3.75 +"px"
window.onresize = function(){
document.documentElement.style.fontSize =
document.documentElement.clientWidth / 3.75 +"px"
}
复制代码
在homt.html中将文件依次引入:
<link href="css/mui.css" rel="stylesheet" />
<link rel="stylesheet" href="../../css/swiper.css">
<script src = "../../js/rem.js"></script>
<script src="../../js/mui.js"></script>
<script src = "../../js/vue.js"></script>
<script src = "../../js/swiper.js"></script>
复制代码
编写banner结构
<body>
<div id = "app">
<home-banner></home-banner>
</div>
<template id = "home-banner">
<div class="home-banner swiper-container">
<div class = "swiper-wrapper">
<div class = "swiper-slide"></div>
</div>
<div class="swiper-pagination"></div>
</div>
</template>
//注册home-banenr组件
Vue.component("home-banner",{
template:"#home-banner"
})
new Vue({
el:"#app"
})
</script>
</body>
复制代码
mui框架基于htm5plus的XMLHttpRequest,封装了经常使用的Ajax函数,支持GET、POST请求方式,支持返回json、xml、html、text、script数据类型; 本着极简的设计原则,mui提供了mui.ajax方法,并在mui.ajax方法基础上,进一步简化出最经常使用的mui.get()、mui.getJSON()、mui.post()三个方法。
created(){
mui.ajax('https://www.missevan.com/mobileWeb/newHomepage3',{
dataType:'json',//服务器返回json格式数据
success:function(data){
console.log(JSON.stringify(data))
}
});
}
复制代码
在data中声明一个空数组
data(){ //组件里面数据必须是函数的形式 为了让每个实例能够获取一份被返回对象的独立的拷贝
return{
banners:[]
}
}
复制代码
将获取到的数据赋值给banner,这里注意下this指向问题,不能写成普通函数,要写成箭头函数。
success:(data) => {
// console.log(JSON.stringify(data))
this.banners = data.info.banner
}
复制代码
在页面中利用v-for循环渲染数据
<template id = "home-banner">
<div class="home-banner swiper-container">
<div class = "swiper-wrapper">
<div class = "swiper-slide"
v-for= "(banner,index) in banners"
:key = "index"
>
<img width = "100%" :src = "banner.pic" />
</div>
</div>
<div class="swiper-pagination">
</div>
</div>
</template>
复制代码
如今banner还没法滑动,须要实例化,可是会出现轮播图划不动的现象。这是由于咱们须要等到因数据改变,生成虚拟dom,对比完成以后生成真实dom再去进行实例化,因此咱们要将实例化操做写在this.$nextTick中。
//数据改变,生成新的虚拟dom,与上一次虚拟dom结构作对比,对比完成以后,生成好了新的真实dom,而后在这个函数的回调函数内部就能够访问到因数据变化而渲染出来的真实dom结构了,因此就能够进行实例化相关的操做.
this.$nextTick(()=>{
new Swiper (".home-banner",{
loop:true,
pagination:{
el:".swiper-pagination"
}
})
})
复制代码
效果演示:
此模式下应用占用全屏区域,而系统状态栏会拦截用户操做事件,此时须要预留出系统状态栏高度。 获取系统状态栏高度及沉浸式状态判断参考:
如何动态判断沉浸式状态栏模式:
HBuilder建立的应用默认不使用沉浸式状态栏样式,须要进行以下配置开启: 打开应用的manifest.json文件,切换到代码视图,在plus -> statusbar 下添加immersed节点并设置值为true。
"plus" : {
"statusbar" : {
"immersed" : true
}
}
复制代码
注意:
设置系统状态栏样式
void plus.navigator.setStatusBarStyle(style);
www.html5plus.org/doc/zh_cn/n…
说明:设置应用在前台运行时系统状态栏的样式,默认值可经过manifest.json文件的plus->statusbar->style配置。
注意:此操做是应用全局配置,调用的Webview窗口关闭后仍然生效。
参数:
在全局index.html中设置样式
mui.plusReady(function() {
//设置导航条的颜色
plus.navigator.setStatusBarStyle("light")
})
复制代码
分别编写my-sound-box、my-sound、my-sound-item组件,互相嵌套。
<template id = "my-sound-box">
<div class="my-sound-box">
<my-sound>
</my-sound>
</div>
</template>
<template id = "my-sound">
<div class="my-sound">
<div class="panel-head">
<p>日抓</p>
<p>更多</p>
</div>
<div class="panel-body">
<my-sound-item></my-sound-item>
</div>
</div>
</template>
<template id = "my-sound-item">
<div class="my-sound-item">
<div class="img-box">
<img src="" alt="">
</div>
<div class="title"></div>
<div class="detail">
<span class="play-count"></span>
<span class = "comments"></span>
</div>
</div>
</template>
复制代码
当宽度小于330px时利用媒体查询标签添加横向滚动条
@media only screen and (max-width: 330px) {
.my-sound .panel-body{
justify-content: flex-start;
overflow-x: auto;
}
}
复制代码
在最外层组件my-sound-box中请求数据,将获取到的music赋值给sounds。
//注册my-sound-box组件
Vue.component("my-sound-box",{
template:"#my-sound-box",
data(){
return {
sounds:[]
}
},
created(){
mui.ajax('https://www.missevan.com/sound/newhomepagedata',{
dataType:'json',
success:(data)=>{
this.sounds = data.music
}
})
}
})
复制代码
在my-sound-box模版中循环遍历sounds,而且将拿到的值传递给子组件my-sound:
<template id = "my-sound-box">
<div class="my-sound-box">
<my-sound
v-for = "sound in sounds"
:key = "sound.id"
:sound = "sound">
</my-sound>
</div>
</template>
复制代码
my-sound经过props接收my-sound-box传递来的sound:
//注册my-sound组件
Vue.component("my-sound",{
template:"#my-sound",
props:["sound"]
})
复制代码
再在my-sound模板中循环遍历sound.objects_point,而且将item传递给子组件my-sound-item:
<template id = "my-sound">
<div class="my-sound">
<div class="panel-head">
<p>{{sound.title}}</p>
<p>更多</p>
</div>
<div class="panel-body">
<my-sound-item
v-for = "item in sound.objects_point"
:key = "item.id"
:item = "item"
></my-sound-item>
</div>
</div>
</template>
复制代码
my-sound-item经过props接收父组件my-sound传递过来的参数item,在本身的模板中打印对应的数据:
<template id = "my-sound-item">
<div class="my-sound-item">
<div class="img-box">
<img :src="item.cover_image" alt="">
</div>
<div class="title">
{{item.soundstr}}}
</div>
<div class="detail">
<span class="play-count">{{item.view_count}}}</span>
<span class = "comments">{{item.comment_count}}</span>
</div>
</div>
</template>
复制代码
发现问题:图片加载不出来
缘由:图片地址不完整,须要手动拼接字符串
咱们获取到的地址:201906/12/fdc535722aa97844750cbb3843c6ec22152202.jpg 实际图片地址:static.missevan.com/coversmini/…
解决办法:
为了方便维护,在my-sound-item中添加一个计算属性computed,直接返回拼接好的字符串:
Vue.component("my-sound-item",{
template:"#my-sound-item",
props:["item"],
computed:{
getImgUrl(){
let baseDir = "http://static.missevan.com/coversmini/"
return baseDir + this.item.cover_image
}
}
})
复制代码
而后前端页面直接调用计算属性便可,不须要打括号。
<img :src="getImgUrl" alt="">
复制代码
对请求到的数据进行优化,当数值大于10000时显示保留一位小数后加"万"的形式。
filters:{
filterVal(val){
if(val>10000){
val = val/10000;
val = val.toFixed(1);
val = val+"万"
}
return val;
}
}
复制代码
调用数据的时候在后面添加filters:
<div class="detail">
<span class="play-count">{{item.view_count | filterVal}}</span>
<span class = "comments">{{item.comment_count | filterVal}}</span>
</div>
复制代码
showWaiting:显示系统等待对话框
www.html5plus.org/doc/zh_cn/n…
在请求数据以前添加showWaiting等待框:
created(){
plus.nativeUI.showWaiting("等待中...");
mui.ajax('https://www.missevan.com/mobileWeb/newHomepage3',{
dataType:'json',//服务器返回json格式数据
success:(data) => {
// console.log(JSON.stringify(data))
this.banners = data.info.banner
//数据改变,生成新的虚拟dom,与上一次虚拟dom结构作对比,对比完成以后,生成好了新的真实dom,而后在这个函数的回调函数内部就能够访问到因数据变化而渲染出来的真实dom结构了,因此就能够进行实例化相关的操做.
this.$nextTick(()=>{
new Swiper (".home-banner",{
loop:true,
pagination:{
el:".swiper-pagination"
}
})
})
}
});
}
复制代码
设置一个标志isOk,默认是0:
let isOk = 0;
复制代码
在数据请求到的时候,每请求一次执行执行isOk++,当isOk ===2时,执行关闭等待框的方法:
success:(data) => {
// console.log(JSON.stringify(data))
this.banners = data.info.banner
//数据改变,生成新的虚拟dom,与上一次虚拟dom结构作对比,对比完成以后,生成好了新的真实dom,而后在这个函数的回调函数内部就能够访问到因数据变化而渲染出来的真实dom结构了,因此就能够进行实例化相关的操做.
this.$nextTick(()=>{
new Swiper (".home-banner",{
loop:true,
pagination:{
el:".swiper-pagination"
}
})
})
isOk++
if(isOk ===2 ){
plus.nativeUI.closeWaiting("等待中...")
}
}
复制代码
效果演示:
在play文件夹下,新建含mui的html页面,新建Vue实例挂载到div上。
使用mui自带header组件生成头部,添加common-header
class名
<div id="app">
<header class="mui-bar mui-bar-nav common-header">
<a class = "mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">好玩</h1>
</header>
</div>
复制代码
此时头部被系统状态栏挡住,须要调整头部距离页面顶部的高度。
getStatusbarHeight:获取系统状态栏高度
www.html5plus.org/doc/zh_cn/n…
在mui.min.js中编写设置状态栏的方法
function setStatusBar(){
let commonHeader = document.querySelector(".common-header");
let status_bar = plus.navigator.getStatusbarHeight();
commonHeader.style.paddingTop = statusbar + "px"
commonHeader.style.height = 44 + status_bar + "px"
}
复制代码
在前端页面created生命周期中调用此方法
new Vue({
el:"#app",
created(){
setStatusBar()
}
})
复制代码
编写musicbox组件和music组件
<template id = "music-box">
<div class="music-box">
<music></music>
</div>
</template>
复制代码
在music-box中请求数据,编写getMusics请求数据的方法,而且将数据赋值给musics。在created中调用getMusics方法
Vue.component("music-box",{
template:"#music-box",
data(){
return{
musics:[]
}
},
methods:{
getMusics(){
mui.ajax('https://www.missevan.com/explore/tagalbum',{
data:{
order:0
},
dataType:'json',//服务器返回json格式数据
success:(data) => {
this.musics = data.albums
}
});
}
},
created(){
this.getMusics()
}
})
Vue.component("music",{
template:"#music",
props:["music"]
})
复制代码
在music组件中接收父组件传递过来的music,渲染数据到页面上。
<template id = "music-box">
<div class="music-box">
<music
v-for = "music in musics"
:key = "music.id"
:music = "music"
></music>
</div>
</template>
<template id="music">
<div class="music">
<div class="img-box">
<img :src="music.front_cover" alt="">
</div>
<p class = "title">{{music.title}}</p>
</div>
</template>
复制代码
v-for指令循环渲染必需要设置key值
跟diff算法有关,为了提升效率
若是在两个元素之间插入新元素,若是没有key的话须要把原位置的元素卸载了,把新元素插进来,而后依次卸载,会打乱后续元素的排列规则,若是有key值,只须要插入到对应位置便可,不会改变其余元素的走向。
key也为了减免一些出错问题
例如在数组中,原本第一个是选中的,这时候咱们再去添加新的元素,若是没有key的话那么新添加进来的元素就会被选中,加上key就是为了不出现这样的问题。
单webview模式
dev.dcloud.net.cn/mui/pulldow…
引入上拉刷新容器,放入咱们的数据music-box。
<!--下拉刷新容器-->
<div id="refreshContainer" class="mui-content mui-scroll-wrapper">
<div class="mui-scroll">
<!--数据列表-->
<music-box></music-box>
</div>
</div>
复制代码
初始化方法相似下拉刷新,经过mui.init方法中pullRefresh参数配置上拉加载各项参数
created(){
this.getMusics()
mui.init({
pullRefresh : {
container:"#refreshContainer",//待刷新区域标识,querySelector能定位的css选择器都可,好比:id、.class等
up : {
height:50,//可选.默认50.触发上拉加载拖动距离
//默认启动的话就不须要执行this.getMusics()了
//auto:true,//可选,默认false.自动上拉加载一次
contentrefresh : "正在加载...",//可选,正在加载状态时,上拉加载控件上显示的标题内容
contentnomore:'没有更多数据了',//可选,请求完毕若没有更多数据时显示的提醒内容;
//不须要打括号了,打括号就立马执行了
callback :this.getMusics //必选,刷新函数,根据具体业务来编写,好比经过ajax从服务器获取新数据;
}
}
});
}
复制代码
当数据请求成功的时候执行,结束上拉刷新操做,而且执行this.p++,请求第二页数据,在参数中判断当前页码是否大于总页码,将请求到的数据使用concat方法拼接到原数组中,不然新请求到的数据会将原数据覆盖。将this.p 与data.pagination.maxpage(最大页数)作对比,当前页数大于最大页数的时候中止请求。
success:(data) => {
this.musics = this.musics.concat(data.albums)
this.p++;
//若还有更多数据,则传入False,不然传入distribute
mui('#refreshContainer').pullRefresh().endPullupToRefresh(this.p > data.pagination.maxpage);
}
复制代码
双webview模式
dev.dcloud.net.cn/mui/pulldow…
经过两个窗口来实现,在play.html中加载子页面play-content.html
主页面内容比较简单,就只有一个头部,在url中添加下拉刷新内容子页面地址。
new Vue({
el:"#app",
created(){
// setStatusBar()
mui.init({
subpages:[{
url:"./play-content.html",//下拉刷新内容页面地址
id:"play-content.html",//内容页面标志
styles:{
top:44 + plus.navigator.getStatusbarHeight(),//内容页面顶部位置,需根据实际页面布局计算,若使用标准mui导航,顶部默认为48px;
bottom:0//其它参数定义
}
}]
});
}
})
复制代码
建立子页面,下拉刷新的操做写在子页面中:
<body>
<div id="app">
<!--下拉刷新容器-->
<div id="refreshContainer" class="mui-content mui-scroll-wrapper">
<div class="mui-scroll">
<!--数据列表-->
<music-box></music-box>
</div>
</div>
<!-- 置顶 -->
<div @tap="backtop" class = "back-top-box" v-if="isShow">
<div class = "back-top">
<i class = "mui-icon mui-icon-arrowup"></i>
</div>
</div>
</div>
<template id = "music-box">
<div class="music-box">
<music
v-for = "music in musics"
:key = "music.id"
:music = "music"
></music>
</div>
</template>
<template id="music">
<div class="music" @tap = "toAlbum(music.id)">
<div class="img-box">
<img :src="music.front_cover" alt="">
</div>
<p class = "title">{{music.title}}</p>
</div>
</template>
<script src="../../js/mui.min.js"></script>
<script src = "../../js/vue.js"></script>
<script type="text/javascript">
Vue.component("music-box",{
template:"#music-box",
data(){
return{
musics:[],
order:0,
p:1,
tid:0
}
},
mounted(){
window.addEventListener("getTid",e=>{
// console.log(e.detail.tid)
this.changeType(e.detail.tid)
})
},
methods:{
changeType(tid){
this.musics = [];
this.p = 1;
this.tid = tid
this.getMusics();
//重置上拉加载
mui('#refreshContainer').pullRefresh().refresh(true);
//须要实现滚动到顶部
mui("#refreshContainer").pullRefresh().scrollTo(0,0);
},
getMusics(){
// let {order,p} = this;
let data;
if(this.tid === 0){ //说明是所有分类
data = {order:this.order,p:this.p}
}else{
data = {order:this.order,p:this.p,tid:this.tid}
}
mui.ajax('https://www.missevan.com/explore/tagalbum',{
data,
dataType:'json',//服务器返回json格式数据
success:(data) => {
this.musics = this.musics.concat(data.albums)
this.p++;
//若还有更多数据,则传入False,不然传入distribute
mui('#refreshContainer').pullRefresh().endPullupToRefresh(this.p > data.pagination.maxpage);
}
});
}
},
created(){
//this.getMusics()
mui.init({
pullRefresh : {
container:"#refreshContainer",//待刷新区域标识,querySelector能定位的css选择器都可,好比:id、.class等
up : {
height:50,//可选.默认50.触发上拉加载拖动距离
//默认启动的话就不须要执行this.getMusics()了
auto:true,//可选,默认false.自动上拉加载一次
contentrefresh : "正在加载...",//可选,正在加载状态时,上拉加载控件上显示的标题内容
contentnomore:'没有更多数据了',//可选,请求完毕若没有更多数据时显示的提醒内容;
//不须要打括号了,打括号就立马执行了
callback :this.getMusics //必选,刷新函数,根据具体业务来编写,好比经过ajax从服务器获取新数据;
}
}
});
}
})
Vue.component("music",{
template:"#music",
props:["music"],
methods:{
toAlbum(albumId){
//打开album.html这个窗口
mui.openWindow({
url:"../album/album.html",
id:"album.html",
extras:{
albumId
},
styles:{
//设置一个渐变式导航栏
"titleNView":{
backgroundColor: '#234245',//导航栏背景色
titleText: '猫耳FM',//导航栏标题
titleColor: '#fff',//文字颜色
type:'transparent',//透明渐变样式
autoBackButton: true,//自动绘制返回箭头
splitLine:{//底部分割线
color:'#cccccc'
}
}
}
})
}
}
})
new Vue({
el:"#app",
data:{
isShow:false
},
methods:{
backtop(){
//需实现滚动到顶部
mui('#refreshContainer').pullRefresh().scrollTo(0,0,100)
}
},
mounted(){ //能够拿到真实dom
//监听滚动事件
document.querySelector('#refreshContainer' ).addEventListener('scroll', (e)=>{
var scroll = mui('#refreshContainer').pullRefresh(); //获取具体容器的滚动条
// console.log(scroll.y);
if(scroll.y <= -300 && !this.isShow){
this.isShow = true;
}else if(scroll.y > -300 && this.isShow){
this.isShow = false;
}
})
}
})
</script>
</body>
复制代码
效果演示:
编写返回顶部按钮,添加点击事件。
<!-- 返回顶部 -->
<div @tap="backtop" class = "back-top-box">
<div class = "back-top">
<i class = "mui-icon mui-icon-arrowup"></i>
</div>
</div>
复制代码
编写返回顶部方法
methods:{
backtop(){
//需实现滚动到顶部
mui('#refreshContainer').pullRefresh().scrollTo(0,0,100)
}
}
复制代码
mui提供的返回顶部的方法
一开始不让返回顶部按钮显示,在data中定义一个数据isShow:false
,经过v-if指令来控制按钮的现实与隐藏,当滚动到必定高度的时候再显示出来。
<div @tap="backtop" class = "back-top-box" v-if="isShow">
<div class = "back-top">
<i class = "mui-icon mui-icon-arrowup"></i>
</div>
</div>
复制代码
在mounted钩子函数中监听滚动事件(注意不能写在created生命周期中,由于created中获取不到真实dom)
mounted(){ //能够拿到真实dom
//监听滚动事件
document.querySelector('#refreshContainer' ).addEventListener('scroll', function (e ) {
var scroll = mui('#refreshContainer').scroll();
console.log(scroll.y);
})
}
复制代码
因为咱们使用的是双web view模式,因此会出现两个滚动条,须要改为:
mounted(){ //能够拿到真实dom
//监听滚动事件
document.querySelector('#refreshContainer' ).addEventListener('scroll', function (e ) {
var scroll = mui('#refreshContainer').pullRefresh(); //获取具体容器的滚动条
console.log(scroll.y);
})
}
复制代码
因为向下滚动是负值,因此须要判断,数值小于等于-300的时候给isShow赋值为true让返回顶部按钮显示,反之则为false,不显示。同时须要将普通函数function改成箭头函数,不然this指向有问题。
mounted(){ //能够拿到真实dom
//监听滚动事件
document.querySelector('#refreshContainer' ).addEventListener('scroll', (e)=>{
var scroll = mui('#refreshContainer').pullRefresh(); //获取具体容器的滚动条
// console.log(scroll.y);
if(scroll.y <= -300){
this.isShow = true;
}else{
this.isShow = false;
}
})
}
复制代码
为了避免让isShow频繁的赋值,给if添加判断条件:
if(scroll.y <= -300 && !this.isShow){
this.isShow = true;
}else if(scroll.y > -300 && this.isShow){
this.isShow = false;
}
复制代码
效果演示:
搭建mine个人页面,添加登陆按钮,给登陆按钮添加点击事件,点击跳转到login登陆页面。
<div id="app">
<div class="user-info">
<div class="login-info">
<div class="img-box">
<img :src="getUserimg" alt="">
</div>
<p v-if = "!userInfo" class = "login"><button @tap = "login ">登陆</button></p>
<p v-else class = "username">{{userInfo.nickname}}</p>
</div>
<div class = "exit" @tap= "exit"><i class = "mui-icon mui-icon-more"></i></div>
</div>
</div>
复制代码
mui提供登陆模版,右键——新建项目选择带登陆和设置的MUI项目模板。
咱们须要在mine页面打开login新页面,将login.html页面添加到咱们pages中login目录下,修改文件引入路径。
打开新页面方法:
dev.dcloud.net.cn/mui/window/…
在methods中编写打开login页面的方法:
new Vue({
el:"#app",
methods:{
login(){
mui.openWindow({
url:"../login/login.html",
id:"login.html",
styles:{
top:0,//新页面顶部位置
bottom:0,//新页面底部位置
},
show:{
autoShow:true,//页面loaded事件发生后自动显示,默认为true
aniShow:"slide-in-top",//页面显示动画,默认为”slide-in-right“;
duration:2000//页面动画持续时间,Android平台默认100毫秒,iOS平台默认200毫秒;
}
})
}
}
})
复制代码
在h5+中查看AnimationTypeShow的方法:一组用于定义页面或控件显示动画效果
OAuth模块管理客户端的用户登陆受权验证功能,容许应用访问第三方平台的资源。
getServices:获取登陆受权认证服务列表
www.html5plus.org/doc/zh_cn/o…
Hbuilder目前支持的第三方登陆列表有QQ、微信、新浪微博,因此for循环以后就会把Hbuilder内部支持的第三方登陆列表跟咱们所期待的(var authBtns = ['qihoo', 'weixin', 'sinaweibo', 'qq'];)进行一个匹配,若是匹配上以后,它就会在页面上渲染图标,而且给图标自动绑定一个authId,这个authId和你提供的service.id匹配上了才追加上去,另外它还对weixin的id进行了强制性判断,若是service.id名称叫weixin而且未安装,就添加disabled禁用。
$.plusReady(function() {
plus.screen.lockOrientation("portrait-primary");
//第三方登陆 定义了须要支持的第三方登陆名称
var authBtns = ['qihoo', 'weixin', 'sinaweibo', 'qq']; //配置业务支持的第三方登陆
var auths = {};
var oauthArea = doc.querySelector('.oauth-area');
plus.oauth.getServices(function(services) {
//终端支持的登陆受权认证服务列表
//因此hbuilder第三方服务认证列表目前支持的Services:weixin sinaweibo qq
for (var i in services) {
var service = services[i];
auths[service.id] = service;//{weixin:weixinService,qq:qqService}
if (~authBtns.indexOf(service.id)) { //==if (authBtns.indexOf(service.id) > -1)
var isInstalled = app.isInstalled(service.id);
var btn = document.createElement('div');
//若是微信未安装,则为不启用状态
btn.setAttribute('class', 'oauth-btn' + (!isInstalled && service.id === 'weixin' ? (' disabled') : ''));
btn.authId = service.id;
btn.style.backgroundImage = 'url("../../images/' + service.id + '.png")'
oauthArea.appendChild(btn);
}
}
复制代码
经过事件委托给按钮添加点击事件,经过getUserInfo方法获取用户信息,并存储在localStorage中。
//事件委托
$(oauthArea).on('tap', '.oauth-btn', function() {
if (this.classList.contains('disabled')) {
plus.nativeUI.toast('您还没有安装微信客户端');
return;
}
var auth = auths[this.authId];
var waiting = plus.nativeUI.showWaiting();
auth.login(function() {
waiting.close();
plus.nativeUI.toast("登陆认证成功");
auth.getUserInfo(function() {
plus.nativeUI.toast("获取用户信息成功");
var name = auth.userInfo.nickname || auth.userInfo.name;
//nickname headimgurl
localStorage.userInfo = JSON.stringify(auth.userInfo)
}, function(e) {
plus.nativeUI.toast("获取用户信息失败:" + e.message);
});
}, function(e) {
waiting.close();
plus.nativeUI.toast("登陆认证失败:" + e.message);
});
});
复制代码
在mine页面中声明一个data,userInfo用来存放用户数据,在methods中添加从localStorage中获取用户信息的方法。
getUserInfo(){
this.userInfo = JSON.parse(localStorage.userInfo ? localStorage.userInfo : "null")
}
复制代码
给p标签添加v-if/v-else指令,经过userInfo来控制登陆按钮的显示与隐藏。
<p v-if = "!userInfo" class = "login"><button @tap = "login ">登陆</button></p>
<p v-else class = "username">{{userInfo.nickname}}</p>
复制代码
这时候系统会报错,说是没有办法给图片赋值为空,因此在页面中获取图片属性的时候,须要等到数据请求到时再获取,这时候添加一个计算属性getUserimg,判断一下是否有图片信息,若是没有图片信息则使用未登陆默认图片。
computed:{
getUserimg(){
return this.userInfo?this.userInfo.headimgurl:"../../images/"
}
}
复制代码
在页面中调用计算属性getUserimg,获取图片数据。
<img :src="getUserimg" alt="">
复制代码
*** 注意:须要从新启动程序才能够看到头像和用户名,由于从mine页面跳入login窗口的时候,mine窗口没有被销毁,因此没有走created生命周期函数。**
AuthService:登陆受权认证服务对象
www.html5plus.org/doc/zh_cn/o…
编写exit方法,使用h5+的actionSheet方法
actionSheet:弹出系统选择按钮框
www.html5plus.org/doc/zh_cn/n…
ActionSheetCallback:系统选择按钮框的回调函数
www.html5plus.org/doc/zh_cn/n…
在回调函数中咱们能够拿到用户点击的项目下标,数据类型为number,根据返回的数值进行switch判断,当点击注销登陆的时候,清除localStorage中的用户信息,而且从新执行getUserInfo,当点击切换帐号时直接跳转到登陆页面(注意箭头函数的this指向问题)。
exit(){
plus.nativeUI.actionSheet(
{
title:"Plus is ready!",
cancel:"取消",
buttons:[
{
style:"destructive",
title:"注销登陆"
},
{
title:"切换登陆"
}
]},
(e)=>{
console.log("User pressed: "+e.index);
switch(e.index){
case 1:
localStorage.removeItem("userInfo",null)//清除用户信息
this.getUserInfo()//从新执行,页面从新渲染
break;
case 2:
this.login() //点击切换帐号直接跳到登陆页面
break;
}
}
);
}
复制代码
效果演示:
咱们点击第三方登陆,登陆成功以后返回mine页面应该显示用户信息,可是没有显示,缘由是这个组件没有进行销毁,没有销毁咱们看不到结果,由于created只会执行一次,如今咱们就要使用mui中提供的窗口之间的通讯。
添加自定义事件
dev.dcloud.net.cn/mui/event/#…
咱们在mine页面中的mounted钩子函数中添加一个监听自定义事件,等待这个事件被触发,初始化的时候会执行这个函数,定义一个方法:"login:end",回调函数中获取用户信息。
mounted(){
//定义自定义事件
window.addEventListener("login:end",e=>{
this.getUserInfo()
console.log(e.detail.a)
})
}
复制代码
在login页面,登陆成功以后须要调用mine页面中的login:end方法,经过mui.fire能够触发目标窗口的自定义事件。咱们须要给它传递三个参数
因为咱们以前在总的index.html中定义了id,因此这里能够经过getWebviewById的方法得到相应的webView
//触发mine.html里面login:end方法
let mine = plus.webview.getWebviewById("mine.html")
mui.fire(mine,"login:end",{a:100})
复制代码
效果演示:
在play文件夹中新建一个play-type页面,咱们但愿在play页面点击右上角三个点打开play-type页面,给a标签添加点击事件。
<a @tap = "changeType" class = "mui-icon-more mui-icon mui-icon-left-nav mui-pull-right"></a>
复制代码
在methods中编写changeType方法,调用mui中的打开新页面方法
dev.dcloud.net.cn/mui/window/…
methods:{
changeType(){
//打开新窗口
mui.openWindow({
url:"./play-type.html",
id:"play-type.html",
styles:{
bottom:0,//新页面底部位置
height:260
},
show:{
autoShow:true,//页面loaded事件发生后自动显示,默认为true
aniShow:"slide-in-bottom",//页面显示动画,默认为”slide-in-right“;
duration:200//页面动画持续时间,Android平台默认100毫秒,iOS平台默认200毫秒;
}
})
复制代码
找到H5+中的Webview方法中的WebviewObject:Webview窗口对象,用于操做加载HTML页面的窗口
www.html5plus.org/doc/zh_cn/w…
setStyle方法:
www.html5plus.org/doc/zh_cn/w…
查看传递的参数
经过currentWebview得到当前窗体,给当前窗体经过setStyle设置遮罩层。
//设置遮罩层
let self = plus.webview.currentWebview()
self.setStyle({mask:'rgba(0,0,0,0.5)'})
复制代码
添加关闭遮罩层事件,当点击遮罩层的时候让遮罩层消失,而且让play-type页面关闭
mounted(){
//绑定自定义事件
let self = plus.webview.currentWebview()
self.addEventListener('maskClick', function(){ //点击遮罩层
self.setStyle({mask:'none'}); //让遮罩层消失
plus.webview.getWebviewById("play-type.html").close();//让play-type窗口关闭
},false);
}
复制代码
在data中声明musicType
data:{
musicType:null
}
复制代码
在created钩子函数中使用ajax请求数据
created(){
mui.ajax({
url:"https://www.missevan.com/malbum/recommand",
dataType:"json",
success:(data)=>{
console.log(JSON.stringify(data))
//{"success":true,"info":{"情感":[[170,"热血"],[28,"治愈"],[4421,"抖腿"]],"场景":[[26310,"玩游戏"],[26311,"运动听"],[25,"做业向"]],"主题":[[370,"OP"],[376,"ED"],[273,"翻唱"],[5,"古风"],[850,"同人音乐"],[13349,"游戏原声"],[4,"广播剧"]]}}
this.musicType = data.info;
}
})
}
复制代码
在页面中经过v-for循环渲染数据
<div class="play-type-box">
<div
class="play-type"
v-for = "(value,key,index) in musicType"
:key = "index"
>
<span>{{key}}</span>
<button
v-for = "(item,i) in value"
:key = "i"
>{{item[1]}}</button>
</div>
</div>
复制代码
在musicType中添加一个"所有音单"数据
this.musicType["所有"] = [[0,"所有音单"]]
复制代码
若是咱们想让所有音单在前面显示就须要将这条语句写在前面,可是以后赋值会将它覆盖,因此咱们须要将musicType赋值为空数组,而后用ES6中的展开符将它展开,而后再展开data.info。
this.musicType["所有"] = [[0,"所有音单"]]
this.musicType = {...this.musicType,...data.info}
复制代码
或者经过ES5中的Object.assign方法将数据合并
//或者经过ES5中的assign方法
this.musicType = Object.assign({},this.musicType,data.info)
复制代码
添加一个点击事件,执行关闭功能
<div class="close" @tap="close">
<i class = "mui-icon mui-icon-closeempty"></i>
</div>
复制代码
咱们须要调用play.html中的方法来关闭play-type页面
在play中将关闭窗体的方法,单独封装为
closeType(self){
self.setStyle({mask:'none'});//让遮罩层消失
plus.webview.getWebviewById("play-type.html").hide();//让play-type窗口关闭
}
复制代码
在mounted钩子函数中调用,而且将"close:type"方法传递给play-type.html
mounted(){
//绑定自定义事件
let self = plus.webview.currentWebview()
self.addEventListener('maskClick', (e)=>{//点击遮罩层
this.closeType(self)
},false);
//绑定自定义事件
window.addEventListener("close:type",e=>{
this.closeType(self)
})
}
复制代码
在play-type页面中的close方法中经过mui.fire来调用该方法
methods:{
close(){
//须要关闭遮罩层与play-type.html
let play = plus.webview.getWebviewById("play.html")
mui.fire(play,"close:type")
}
}
复制代码
默认让它选中所有音单,在data中声明一条数据activeId,默认是0。
data:{
musicType:{},
activeId:0
}
复制代码
在button按钮上动态添加class,判断当前Id是否等于activeId,若是相等,则添加class。
<button
v-for = "(item,i) in value"
:key = "i"
:class = "{'mui-btn-danger' : item[0] === activeId}"
>{{item[1]}}</button>
复制代码
给按钮添加点击事件,将当前id变成activeId,实现点击相应按钮出现选中状态。
<button
v-for = "(item,i) in value"
:key = "i"
:class = "{'mui-btn-danger' : item[0] === activeId}"
@click = "activeId = item[0]"
>{{item[1]}}</button>
复制代码
可是当咱们关闭列表页面再打开的时候,选中状态又变回了所有音单,这是由于咱们关闭列表页的时候这个组件被销毁了,activeId又变回了0,因此咱们在closeType方法中不能使用close方法,须要使用hide隐藏方法。
hide:隐藏Webview窗口
www.html5plus.org/doc/zh_cn/w…
closeType(self){
self.setStyle({mask:'none'}); //让遮罩层消失
// plus.webview.getWebviewById("play-type.html").close();//让play-type窗口关闭
plus.webview.getWebviewById("play-type.html").hide();//让play-type窗口隐藏
}
复制代码
当咱们点击按钮的时候activeId发生变化,这时候咱们须要后面的play-content请求相应数据,因此ajax中的data须要发生变化,要获取url相应的id,在play-content的mounted中添加一个事件监听。
mounted(){
window.addEventListener("getTid",e=>{
console.log(e.detail.tid)
})
}
复制代码
咱们须要编写一个changeType方法,在mounted钩子函数中调用,而且将play-type传递过来的tid做为参数传递过去。
mounted(){
window.addEventListener("getTid",e=>{
// console.log(e.detail.tid)
this.changeType(e.detail.tid)
})
}
复制代码
在play-type添加一个watch监听,将activeId最新的值val传递给play-content,当咱们的数据一旦变化它就能够获取到对应的tid。
watch:{
activeId(val){
let playContent = plus.webview.getWebviewById("play-content.html")
mui.fire(playContent,"getTid",{tid:val})
}
}
复制代码
编写changeType方法,在音单类型改变的时候须要将musics清空,p页码变为1,tid变为传递过来的tid,而且从新执行请求数据操做,调用getMusics方法。
changeType(tid){
this.musics = [];
this.p = 1;
this.tid = tid
this.getMusics();
}
复制代码
在getMusics中对data数据进行判断,当tid为0的时候显示所有音单,传递order和p字段过去,当tid不为0的时候显示相对应的数据,而且将tid传递过去。
let data;
if(this.tid === 0){ //说明是所有分类
data = {order:this.order,p:this.p}
}else{
data = {order:this.order,p:this.p,tid:this.tid}
}
复制代码
将data传递给mui.ajax:
mui.ajax('https://www.missevan.com/explore/tagalbum',{
data,
dataType:'json',//服务器返回json格式数据
success:(data) => {
this.musics = this.musics.concat(data.albums)
this.p++;
//若还有更多数据,则传入False,不然传入distribute
mui('#refreshContainer').pullRefresh().endPullupToRefresh(this.p > data.pagination.maxpage);
}
});
复制代码
出现问题:从别的页面跳转到所有音单页面上拉加载失效
缘由:若是别的页面只有一页数据,切换到所有音单的时候,也认为数据请求完毕了,就把上拉加载功能禁用了
解决办法:重置上拉加载
changeType(tid){
this.musics = [];
this.p = 1;
this.tid = tid
this.getMusics();
//重置上拉加载
mui('#refreshContainer').pullRefresh().refresh(true);
}
复制代码
出现问题:从所有音单跳转到别的页面时数据从上方加载进来
缘由:所有音单的页码为第三页,跳转到别的页面的第一页
解决办法:每次切换的时候让它滚动到最上面,从头部开始加载数据
changeType(tid){
this.musics = [];
this.p = 1;
this.tid = tid
this.getMusics();
//重置上拉加载
mui('#refreshContainer').pullRefresh().refresh(true);
//须要实现滚动到顶部
mui("#refreshContainer").pullRefresh().scrollTo(0,0);
}
复制代码
每次切换完毕应该让列表页关闭
在play-type中的watch监听里调用下封装好的close方法,关闭遮罩与play-type窗口。
watch:{
activeId(val){
let playContent = plus.webview.getWebviewById("play-content.html")
mui.fire(playContent,"getTid",{tid:val})
//关闭遮罩与play-type窗口
this.close()
}
}
复制代码
在play-content中的上拉加载操做中有一个auto属性,若是注释掉请求数据的操做this.getMusics,将auto属性设置为true,会默认执行一次上拉加载,效果与请求数据操做相同。
created(){
//this.getMusics()
mui.init({
pullRefresh : {
container:"#refreshContainer",//待刷新区域标识,querySelector能定位的css选择器都可,好比:id、.class等
up : {
height:50,//可选.默认50.触发上拉加载拖动距离
//默认启动的话就不须要执行this.getMusics()了
auto:true,//可选,默认false.自动上拉加载一次
contentrefresh : "正在加载...",//可选,正在加载状态时,上拉加载控件上显示的标题内容
contentnomore:'没有更多数据了',//可选,请求完毕若没有更多数据时显示的提醒内容;
//不须要打括号了,打括号就立马执行了
callback :this.getMusics //必选,刷新函数,根据具体业务来编写,好比经过ajax从服务器获取新数据;
}
}
});
}
复制代码
效果演示:
在play-content页面中给每个music专辑绑定一个点击事件toAlbum,在调用它的时候传递music.id。
<div class="music" @tap = "toAlbum(music.id)">
复制代码
编写toAlbum方法,经过mui.openWindow添加打开新页面方法,将albumId传递给album,经过styles设置一个渐变式导航。
Vue.component("music",{
template:"#music",
props:["music"],
methods:{
toAlbum(albumId){
//打开album.html这个窗口
mui.openWindow({
url:"../album/album.html",
id:"album.html",
extras:{
albumId
},
styles:{
//设置一个渐变式导航栏
"titleNView":{
backgroundColor: '#234245',//导航栏背景色
titleText: '猫耳FM',//导航栏标题
titleColor: '#fff',//文字颜色
type:'transparent',//透明渐变样式
autoBackButton: true,//自动绘制返回箭头
splitLine:{//底部分割线
color:'#cccccc'
}
}
}
})
}
}
})
复制代码
在album页面中let self = plus.webview.currentWebview(),经过self.albumId就能够拿到传递过来的参数,参数命名的时候不要直接命名id,否则他会优先打印当前窗口文件的id(album.html),而不是你传递过来的参数。在mui.ajax中获取数据。
created(){
let self = plus.webview.currentWebview()
// console.log(self.albumId)
mui.ajax({
url:"https://www.missevan.com/sound/soundalllist",
data:{
albumid:self.albumId
},
dataType:"json",
success:data => {
this.album = data.info.album;
this.owner = data.info.owner;
}
})
}
复制代码
在前端页面将数据渲染输出:
<div class="album-box">
<div class="album-bg">
<img :src="album.front_cover" alt="">
</div>
<div class="img-box">
<img :src="album.front_cover" alt="">
</div>
<div class="album-info">
<p class = "title">{{album.title}}</p>
<p class="auther">
<img class = "headimg" :src="owner.boardiconurl2" alt="">
<span class = "nickname">{{album.username}}</span>
</p>
</div>
</div>
复制代码
在data中声明sounds和sound数据:
data:{
album:{},
owner:{},
sounds:[],
sound:[]
}
复制代码
请求数据的时候给sounds赋值,而且经过.splice方法切分出十条数据复制给sound。
success:data => {
this.album = data.info.album;
this.owner = data.info.owner;
this.sounds = data.info.sounds;//[0,115] ==> [10,115]
this.sound = this.sounds.splice(0,10) //[0,9]
}
复制代码
在页面上渲染数据
<div class="album-list">
<div class="album-list-item"
v-for = "item in sound"
:key = "item.id"
@tap = "toDetail(item.id)"
>
<div class="img-box">
<img :src="item.front_cover" alt="">
</div>
<div class="album-detail">
<p class = "title">{{item.soundstr}}</p>
<p class="album-desc">
<span class="play">{{item.view_count_formatted}}</span>
<span class="time">{{item.duration}}</span>
</p>
</div>
</div>
</div>
复制代码
在data中声明cHeight和pHeight
data:{
album:{},
owner:{},
sounds:[],
sound:[],
cHeight:"",
pHeight:""
}
复制代码
在mounted钩子函数中绑定滚动事件,在当前窗口高度document.documentElement.clientHeight
+滚动高度document.body.scrollTop || document.documentElement.scrollTop()
+距离底部高度>整个文档的高度document.documentElement.offsetHeight
的时候在以前的基础上追加十条数据。
mounted(){
window.addEventListener("scroll",e=>{
let sTop = document.body.scrollTop || document.documentElement.scrollTop()//获取滚动高度
this.cHeight = document.documentElement.clientHeight;//获取当前可视区域的高度
this.pHeight = document.documentElement.offsetHeight;//获取整个文档的高度
if(this.cHeight+sTop+50 >= this.pHeight){//50:距离底部的高度
// console.log("滚动到底部了")
//在以前的基础上追加十条数据
this.sound = this.sound.concat(this.sounds.splice(0,10))
}
})
}
复制代码
发现问题:当数据加载完毕的时候向下滚动会继续加载
解决办法:将滚动监听单独封装一个方法listenScroll
methods:{
listenScroll(e){
let sTop = document.body.scrollTop || document.documentElement.scrollTop;
this.cHeight = document.documentElement.clientHeight;
this.pHeight = document.documentElement.offsetHeight;
if(this.cHeight+sTop+50 >= this.pHeight){
console.log("滚动到底部了!")
this.sound = this.sound.concat(this.sounds.splice(0,10))
}
}
}
复制代码
在页面初始化的时候添加事件监听。
mounted(){
window.addEventListener("scroll",this.listenScroll)
}
复制代码
添加一个watch监听,监听sounds的变化,当sounds数组长度为0的时候,移除事件。
watch:{
sounds(val){
if(val.length === 0){
window.removeEventListener("scroll",this.listenScroll)
}
}
}
复制代码
效果演示:
在album中添加点击事件toDetail,将detailId传递给detail.html。
<div class="album-list-item"
v-for = "item in sound"
:key = "item.id"
@tap = "toDetail(item.id)"
>
复制代码
编写toDetail方法,打开对应的页面。
toDetail(detailId){
mui.openWindow({
url:"../detail/detail.html",
extras:{
detailId
}
})
}
复制代码
在详情页detail.html中经过self.detailId打开相应的目标窗口。
new Vue({
el:"#app",
created(){
let self = plus.webview.currentWebview();
mui.openWindow({
url:"https://m.missevan.com/sound/"+self.detailId,
id:"detail.html"
})
}
})
复制代码
如今咱们想将头部的猫耳FM删掉
咱们在控制台中输入两条语句,去掉当前窗口的头部导航栏
咱们声明一个参数,返回一个窗体对象
let albumDetail = mui.openWindow({
url:"https://m.missevan.com/sound/"+self.detailId,
id:"detail.html"
})
复制代码
调用对象的onloaded方法,当窗体载入的时候执行这两条js语句
evalJS
调用evalJS在Webview窗口中执行JS脚本
www.html5plus.org/doc/zh_cn/w…
albumDetail.onloaded = function(){
albumDetail.evalJS('document.getElementsByTagName("header")[0].innerHTML = "";document.getElementsByTagName("container")[0].style.padding = 0')
}
复制代码
在style中设置一个渐变式导航栏
styles:{
//设置一个渐变式导航栏
"titleNView":{
backgroundColor: '#234245',//导航栏背景色
titleColor: '#fff',//文字颜色
type:'transparent',//透明渐变样式
autoBackButton: true,//自动绘制返回箭头
splitLine:{//底部分割线
color:'#cccccc'
}
}
}
复制代码
在success中经过self.setStyle方法设置成titleNView的样式,让头部显示对应的标题。
self.setStyle({
"titleNView":{
titleText:this.album.title
}
})
复制代码
编写filter过滤器,将毫秒数转成"分钟:秒数"的形式。
filters:{
timer(val){
val = val/1000;
let second = Math.ceil(val%60)
let min = parseInt(val/60)
second = second < 10 ? "0" + second:second
return min + ":" + second
}
}
复制代码
在时间展现的span标签中应用
<span class="time">{{item.duration | timer}}</span>
复制代码
效果演示:
上线发布以前咱们须要在manifest.json中针对App进行设置,设置内容分别为:
HBuilder提供的打包有云打包和本地打包两种。 HBuilder提供的云打包对正常开发者是免费的。但过多浪费服务器资源会额外收费。用本地打包无任何限制。 云打包的特色是DCloud官方配置好了原生的打包环境,能够把HTML等文件编译为原生安装包。
在manifest.json中配置完毕就能够进行云端打包了,你只须要提交代码,不用部署xcode和Android sdk就能够打包应用。打包完毕下载安装,打包速度取决于你的网速。
文件名右键选择【发行】——原生APP云打包
选择IOS平台或者Andriod平台,若是只是本身测试可使用DCloud的公用证书,可是不能发布上线。已经打好的安装包,容许开发者在指定天内下载指定次数。超时或超次后服务器端会清除文件。
打包成功后会生成下载地址,点击下载后就能够进行安装使用了。