Huilder X開發-貓耳APP(H5+/MUI/VUE)

image

前言

近年來國內出現了一些可讓前端人員編寫移動端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

前端項目地址:https://github.com/Hanxueqing...html

在線項目手冊:https://hanxueqing.github.io/...前端

項目技術棧

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開啓真機演示

項目開發

環境搭建

下載安裝HBuilder X

在官網地址選擇合適的版本下載安裝:git

http://www.dcloud.io/hbuilder...github

image

新建項目

打開HBuilder,在菜單欄中選擇文件——新建——項目,選擇5+App,建立一個mui項目,填寫文件名稱、保存位置,點擊建立,會給你生成一個包含mui的js、css、字體資源的項目模版。

文件結構

新建完成後,會在左側的項目管理器中出現以下目錄結構,跟咱們平時作前端開發的項目相似。mainifest.json文件中存儲的是app相關的配置。

真機調試

使用數據線鏈接手機和電腦,在Android設備會自動安裝並啓動HBuilder調試基座,IOS系統的同窗請下載一個名字叫HBuilder的調試插件,點擊窗口上方的播放鍵小圖標或者使用快捷鍵command+r在手機上運行。

真機運行有3個特色:

  1. 真實。雖然PC端HBuilder右側的內置瀏覽器也能夠看大體的頁面,但真實的佈局效果以及手機上的特殊能力調用,仍是必須在真機測試。
  2. 邊改邊看。在HBuilder更改頁面並保存後,可當即同步在真機上看到保存後的顯示效果。比開發原生應用還方便。
  3. 檢查錯誤和log。手機運行HTML等文件時若是發生錯誤以及打印的console.log,均可以在真機運行時從手機端反饋回到HBuilder的控制檯,在控制檯直接查看。
    注意只有移動App項目才能夠真機聯調。

若是你真機失敗,注意看控制檯的提示,或點HBuilder菜單-運行裏的故障排查指南。
注意:真機聯調App時,提供的是一個測試環境,並不真實發生打包,調試基座App的名字、圖標、啓動封面圖片、是否可旋轉這些只有打包才能更改的屬性不會由於開發者修改manifest文件而變化。只有修改manifest且點擊菜單發行-打包後,上述4個設置才能更改。

運行後,HBuilder中修改頁面代碼,保存後會自動同步到手機中,若是手機當前展現着被修改的頁面,則會刷新頁面。嘗試在js中在plus ready以後編寫console.log,或者改寫錯誤的js,能夠直接在HBuilder的控制檯看到結果。若是真機運行遇到各類故障,請點擊運行菜單裏的真機運行常見故障指南。

底部Tab選項卡

頁面初始化

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

http://www.html5plus.org/doc/...

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

經過Mui.os判斷平臺

http://dev.dcloud.net.cn/mui/...

咱們常常會有經過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');
            }
        });

Banner輪播圖

引入swiper

上bootcdn將swiper的樣式和js文件複製到本地

https://www.bootcdn.cn/Swiper/

此項目咱們要使用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>

利用ajax方法請求數據

mui框架基於htm5plus的XMLHttpRequest,封裝了經常使用的Ajax函數,支持GET、POST請求方式,支持返回json、xml、html、text、script數據類型;
本着極簡的設計原則,mui提供了mui.ajax方法,並在mui.ajax方法基礎上,進一步簡化出最經常使用的mui.get()、mui.getJSON()、mui.post()三個方法。

http://dev.dcloud.net.cn/mui/...

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

效果演示:

image

沉浸式導航欄

http://ask.dcloud.net.cn/arti...

此模式下應用佔用全屏區域,而系統狀態欄會攔截用戶操做事件,此時須要預留出系統狀態欄高度。
獲取系統狀態欄高度及沉浸式狀態判斷參考:

如何動態判斷沉浸式狀態欄模式:

http://ask.dcloud.net.cn/arti...

HBuilder建立的應用默認不使用沉浸式狀態欄樣式,須要進行以下配置開啓:
打開應用的manifest.json文件,切換到代碼視圖,在plus -> statusbar 下添加immersed節點並設置值爲true。

"plus" : {
        "statusbar" : {
            "immersed" : true
        }
 }

注意:

  1. 真機運行不生效,需提交App雲端打包後才生效;
  2. 此功能僅在Android4.4及以上系統有效。

navigator狀態欄樣式

設置系統狀態欄樣式

void plus.navigator.setStatusBarStyle(style);

http://www.html5plus.org/doc/...

說明:設置應用在前臺運行時系統狀態欄的樣式,默認值可經過manifest.json文件的plus->statusbar->style配置。

注意:此操做是應用全局配置,調用的Webview窗口關閉後仍然生效。

參數:

  • style: ( String) 必選* 系統狀態欄樣式
  • 可取值:"dark":深色前景色樣式(即狀態欄前景文字爲黑色),此時background建議設置爲淺顏色; "light":淺色前景色樣式(即狀態欄前景文字爲白色),此時background建設設置爲深顏色;

在全局index.html中設置樣式

mui.plusReady(function() {
                //設置導航條的顏色
                plus.navigator.setStatusBarStyle("light")
        })

my-sound內容區

組件嵌套

分別編寫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
實際圖片地址:http://static.missevan.com/co...

解決辦法:

爲了方便維護,在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="">

filters過濾器

對請求到的數據進行優化,當數值大於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:顯示系統等待對話框

http://www.html5plus.org/doc/...

在請求數據以前添加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("等待中...")
                            }
                        }

效果演示:

image

好玩頁面

建立頁面

在play文件夾下,新建含mui的html頁面,新建Vue實例掛載到div上。

設置頭部

使用mui自帶header組件生成頭部,添加common-headerclass名

<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:獲取系統狀態欄高度

http://www.html5plus.org/doc/...

在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

編寫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值

  1. 跟diff算法有關,爲了提升效率

    若是在兩個元素之間插入新元素,若是沒有key的話須要把原位置的元素卸載了,把新元素插進來,而後依次卸載,會打亂後續元素的排列規則,若是有key值,只須要插入到對應位置便可,不會改變其餘元素的走向。

  2. key也爲了減免一些出錯問題

    例如在數組中,原本第一個是選中的,這時候咱們再去添加新的元素,若是沒有key的話那麼新添加進來的元素就會被選中,加上key就是爲了不出現這樣的問題。

上拉加載功能

單webview模式

http://dev.dcloud.net.cn/mui/...

引入上拉刷新容器,放入咱們的數據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模式

http://dev.dcloud.net.cn/mui/...

經過兩個窗口來實現,在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>

效果演示:

image

返回頂部

編寫返回頂部按鈕,添加點擊事件。

<!-- 返回頂部 -->
            <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提供的返回頂部的方法

image

一開始不讓返回頂部按鈕顯示,在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)

https://www.cnblogs.com/xzzzy...

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

效果演示:

image

mine頁面

搭建頁面

搭建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項目模板。

image

咱們須要在mine頁面打開login新頁面,將login.html頁面添加到咱們pages中login目錄下,修改文件引入路徑。

打開新頁面方法:

http://dev.dcloud.net.cn/mui/...

在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的方法:一組用於定義頁面或控件顯示動畫效果

http://www.dcloud.io/docs/api...

第三方登陸

OAuth模塊管理客戶端的用戶登陸受權驗證功能,容許應用訪問第三方平臺的資源。

getServices:獲取登陸受權認證服務列表

http://www.html5plus.org/doc/...

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:登陸受權認證服務對象

http://www.html5plus.org/doc/...

退出功能

編寫exit方法,使用h5+的actionSheet方法

actionSheet:彈出系統選擇按鈕框

http://www.html5plus.org/doc/...

ActionSheetCallback:系統選擇按鈕框的回調函數

http://www.html5plus.org/doc/...

在回調函數中咱們能夠拿到用戶點擊的項目下標,數據類型爲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;
                                    }
                                }
                        );
                    }

效果演示:

image

解決登陸後拿不到用戶信息的問題

咱們點擊第三方登陸,登陸成功以後返回mine頁面應該顯示用戶信息,可是沒有顯示,緣由是這個組件沒有進行銷燬,沒有銷燬咱們看不到結果,由於created只會執行一次,如今咱們就要使用mui中提供的窗口之間的通訊。

添加自定義事件

http://dev.dcloud.net.cn/mui/...

咱們在mine頁面中的mounted鉤子函數中添加一個監聽自定義事件,等待這個事件被觸發,初始化的時候會執行這個函數,定義一個方法:"login:end",回調函數中獲取用戶信息。

mounted(){
                //定義自定義事件
                    window.addEventListener("login:end",e=>{
                        this.getUserInfo()
                        console.log(e.detail.a)
                    })
                }

在login頁面,登陸成功以後須要調用mine頁面中的login:end方法,經過mui.fire能夠觸發目標窗口的自定義事件。咱們須要給它傳遞三個參數

image

因爲咱們以前在總的index.html中定義了id,因此這裏能夠經過getWebviewById的方法得到相應的webView

image

//觸發mine.html裏面login:end方法
                                    let mine = plus.webview.getWebviewById("mine.html")
                                    mui.fire(mine,"login:end",{a:100})

效果演示:

image

音單分類

建立音單分類頁面

在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中的打開新頁面方法

http://dev.dcloud.net.cn/mui/...

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頁面的窗口

http://www.html5plus.org/doc/...

setStyle方法:

http://www.html5plus.org/doc/...

查看傳遞的參數

image

經過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)

close關閉功能

添加一個點擊事件,執行關閉功能

<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窗口

http://www.html5plus.org/doc/...

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

重啓上拉加載

出現問題:從別的頁面跳轉到所有音單頁面上拉加載失效

緣由:若是別的頁面只有一頁數據,切換到所有音單的時候,也認爲數據請求完畢了,就把上拉加載功能禁用了

解決辦法:重置上拉加載

image

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從服務器獲取新數據;
                        }
                      }
                    });
                }

效果演示:

image

album詳情頁

點擊跳轉

在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>

請求list數據

在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>

向下滾動獲取數據

image

在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)
                        }
                    }
                }

效果演示:

image

詳情頁

在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刪掉

image

咱們在控制檯中輸入兩條語句,去掉當前窗口的頭部導航欄

image

image

咱們聲明一個參數,返回一個窗體對象

let albumDetail = mui.openWindow({
                        url:"https://m.missevan.com/sound/"+self.detailId,
                        id:"detail.html"
                    })

調用對象的onloaded方法,當窗體載入的時候執行這兩條js語句

evalJS

調用evalJS在Webview窗口中執行JS腳本

http://www.html5plus.org/doc/...

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>

效果演示:

image

打包發佈

配置文件

上線發佈以前咱們須要在manifest.json中針對App進行設置,設置內容分別爲:

  • 基礎配置:主要是基本的應用信息,包含;應用名稱、版本號、入口文件、是否須要根據重力感應橫豎屏。
  • 圖標配置:安裝應用後,顯示在主頁的入口圖標,能夠根據你上傳的大圖標自動壓縮生成各類小圖標。
  • 啓動圖配置:應用啓動後展現給用戶的圖片。
  • SDK配置:這裏是你引用的第三方插件的配置,例如:支付、推送、分享等。
  • 模塊權限配置:你須要使用的原生模塊,去掉不須要的模塊,能夠減小原生安裝包的體積。
  • App經常使用其它設置
  • 源碼視圖

雲端打包

HBuilder提供的打包有云打包和本地打包兩種。
HBuilder提供的雲打包對正常開發者是免費的。但過多浪費服務器資源會額外收費。用本地打包無任何限制。
雲打包的特色是DCloud官方配置好了原生的打包環境,能夠把HTML等文件編譯爲原生安裝包。

在manifest.json中配置完畢就能夠進行雲端打包了,你只須要提交代碼,不用部署xcode和Android sdk就能夠打包應用。打包完畢下載安裝,打包速度取決於你的網速。

文件名右鍵選擇【發行】——原生APP雲打包

image

選擇IOS平臺或者Andriod平臺,若是隻是本身測試可使用DCloud的公用證書,可是不能發佈上線。已經打好的安裝包,容許開發者在指定天內下載指定次數。超時或超次後服務器端會清除文件。

image

打包成功後會生成下載地址,點擊下載後就能夠進行安裝使用了。

image

相關文章
相關標籤/搜索