一個簡單的 vue.js 實踐教程

博客地址:http://cody1991.github.io/vue/2016/08/30/a-simple-vue-guide.htmlcss

一個簡單的 vue.js 實踐教程

更新 (2016.9.6)html


修復 vue-resource 傳參問題前端


更新(2016.9.2)vue


感受須要改善的地方有:jquery

  • (更新代碼)livingInfo 數組和 anchorInfo 數組能夠經過 computed 屬性計算合成一個大的數組,那麼不少的過濾器還有 forEach 遍歷就能夠省略掉了git

  • 能夠把整個 ul 下的部分作成一個組件github

  • 文章可能描述的很囉嗦web


公司有一個項目,其中一部分的截圖以下:ajax

主要需求以下:json

  • 須要拉取十我的的信息,包括封面圖,名字,票數,以及對應用戶是否進行了投票等信息,以及根據票數排序

  • 正在直播的人在右上角會有一個提示

  • 點擊支持的時候,須要反饋給後臺,而且前端這邊會有+1的動畫,以後從新拉取人物信息以及是否正在直播的狀態

  • 每隔一段時間,拉取人物信息以及是否正在直播的狀態

這裏就想到了使用下 vue.js 來構建,由於

  • 人物信息都是後臺拉取的json數據,前端須要展現,若是使用jquery來拼錯DOM結構,或者使用模板來寫,好比BaiduTemplate,都很是繁瑣。使用vue.js的v-for指令能夠簡單的完成這個任務

  • 一開始想要前端這邊進行排序,那麼vue.js的orderBy指令也能夠很簡單的完成排序功能,而不須要額外的代碼判斷(不事後來排序都經過後臺進行了,相應代碼會給出。)

  • 拉取數據,進行先後臺交互,可使用比較成熟的vue-resource代替jquery的$.ajax來操做。

  • 數據會常常進行變化,使用vue.js這樣的MVVM框架,能夠把重點放在數據的操做上,由於數據的更新也會讓DOM保持實時更新

這裏不會講太多vue.js的基礎,由於官網文檔 Getting Started 已經很是完善了。下面開始咱們這個簡單的vue實踐吧。

源碼地址

<div class="container" id="app">
</div>

var app = new Vue({
    el: '#app'
});

上面是最簡單的 vue 實例初始化。

接下來咱們繼續構建咱們的應用

在未使用 vue.js 以前,咱們簡單地使用HTML和CSS重構咱們的項目:

<div class="container" id="app">
    <div class="radio-wrapper">
        <ul class="list clearfix">
            <li>
                <a class="link">
                    <div class="live">
                        <p>觀看直播 ></p>
                    </div>
                    <img src="http://a.impingo.me/static/activity/singer/resource/1616312.jpg" class="user">
                    <img src="./images/play.png" class="play">
                    <p class="add">+1</p>
                </a>
                <div class="user-wrapper">
                    <div class="name">凌兒</div>
                    <div class="num">3280</div>
                </div>
                <div class="do-btn">
                    <p>支持</p>
                </div>
            </li>
            <li>
                <a class="link">
                    <div class="live">
                        <p>觀看直播 ></p>
                    </div>
                    <img src="http://a.impingo.me/static/activity/singer/resource/1616312.jpg" class="user">
                    <img src="./images/play.png" class="play">
                    <p class="add">+1</p>
                </a>
                <div class="user-wrapper">
                    <div class="name">凌兒</div>
                    <div class="num">3280</div>
                </div>
                <div class="do-btn">
                    <p>支持</p>
                </div>
            </li>
        </ul>
    </div>
</div>

大致上的HTML結構就是這樣,配合CSS樣式,能夠獲得下面的輸出結果:

固然如今還都是靜態數據。

ul 裏面的 li ,就須要咱們使用 v-for 指令來進行循環輸出了。下面再繼續說明。

首先來看看咱們一開始的 js 部分的代碼:

var lib = {
    urlParams: function(url) {
        var urlParamsList = {};
        var params = url.search.replace(/^\?/, "").split('&'); //分開成各個不一樣的對像,去掉'&'
        for (var i = 0; i < params.length; i++) {
            var param = params[i];
            var temp = param.split("=");
            urlParamsList[temp[0]] = decodeURI(temp[1]);
        }
        return urlParamsList;
    }
};

window.onload = function() {

    var attachFastClick = Origami.fastclick;
    attachFastClick(document.body);

    var windowLocation = window.location,
        selfUserID = lib.urlParams(windowLocation)['userID'],
        selfSessionID = lib.urlParams(windowLocation)['sessionID'],
        selfSessionToken = lib.urlParams(windowLocation)['sessionToken'],
        selfPeerID = lib.urlParams(windowLocation)['peerID'];

    var app = new Vue({
        el: '#app',
        data: {
            anchorInfo: [],
            getAnchorInfoUrl: "http://a.impingo.me/activity/getAnchorInfo",
        },
        ready: function() {
            this.getAnchorInfo();
        },
        methods: {
            getAnchorInfo: function() {
                this.$http.jsonp(this.getAnchorInfoUrl)
                    .then(function(res) {
                        var rtnData = res.data;
                        if (rtnData.rtn == 0) {
                            this.$set('anchorInfo', rtnData.data);
                        }
                    })
                    .catch(function(res) {
                        console.info('網絡失敗');
                    });
            }
        }
    })
}

lib 對象主要放着一些基礎的方法或者變量,在這裏只有一個解析頁面地址參數的函數 urlParams ,由於後面咱們須要經過頁面地址url獲取投票用戶的userID,即後面看到的

selfUserID = lib.urlParams(windowLocation)['userID'];

selfSessionID,selfSessionToken,selfPeerID不用在乎太多,到時候url沒有傳入這幾個也不要緊。

window.onload 開頭的這段:

var attachFastClick = Origami.fastclick;
    attachFastClick(document.body);

引入了 fastclick,消除手機上點擊的300ms延時。

以後就是咱們上面提到的vue實例了。

咱們給實例添加了新的屬性 data ,它是一個對象,這裏是vue實例存放數據的地方。初始化用戶信息 anchorInfo 爲空數組,以及用戶信息的接口地址 getAnchorInfoUrl 的值爲 http://a.impingo.me/activity/getAnchorInfo

而後就是添加了新的屬性 ready ,它是一個函數,在vue實例初始化完成的時候會調用這個方法。咱們看看這個方法下的代碼:

this.getAnchorInfo();

this 指向vue實例,調用 getAnchorInfo() 方法。

接着往下看,咱們看到一個新的屬性 methods ,它是一個對象,放着咱們vue實例的全部方法。在這之下咱們定義了 getAnchorInfo() 方法。

getAnchorInfo: function() {
    this.$http.jsonp(this.getAnchorInfoUrl)
        .then(function(res) {
            var rtnData = res.data;
            if (rtnData.rtn == 0) {
                this.$set('anchorInfo', rtnData.data);
            }
        })
        .catch(function(res) {
            console.info('網絡失敗');
        });
}

vue-resource 的使用能夠看看這裏,咱們在這裏使用 jsonp 方法請求了 getAnchorInfoUrl 地址的接口,若是請求成功的話,then(function(res)){} ,咱們看看 res 的數據結構

(補充)vue-resource 的 jsonp 基本寫法是(能夠參看官方文檔 HTTP Requests/Response):

this.$http.jsonp(url,{
        params: {
                'someKey': someValue
        }
    })
    // this 是 vue 實例
    // url是請求的地址 params是請求的附帶參數
    .then(function(res){
        // 後臺成功返回數據的時候
        // res 是返回的數據
    })
    .catch(function(res){
        // 後臺響應出錯的時候
    });

res.data 會裝載後臺返回給咱們的數據

能夠看到一些返回的信息,而咱們想要的數據在 res.data 裏面,返回的格式是和後臺協商好的。

看下圖。res.data.rtn 是一個狀態,這裏 0 表明着返回成功。而res.data.data 是一個對象數組,長度爲10,放着十個用戶的信息。每一個對象裏面有屬性 userID,anchorName,supportCnt 分別表明着用戶的ID,用戶的名字以及它的支持度。

res.data.rtn爲0表明成功的狀況下,咱們調用vue的 $set 方法,設置anchorInfo的值,把res.data.data賦給它。在這裏使用$set方法才能保證anchorInfo變量的值在vue裏面是響應式能實時更新的。

接下來咱們修改前面提到的HTML結構吧。咱們從 ul 標籤開始修改。

<ul class="list clearfix" v-cloak>
    <li v-for="anchor in anchorInfo">
                        
    </li>
</ul>

在這裏咱們能夠看到給 ul 標籤加了一個v-cloak,這個是vue實例的DOM結構渲染完成之後,會去掉的一個類。由於咱們常常在vue實例還沒渲染完成的時候會看到一些好比 {{someStr}} 這樣的綁定屬性,咱們在CSS裏面添加

[v-cloak] {
    display: none;
}

那麼在vue實例的DOM還沒渲染完成的時候,就會被隱藏起來了。

接下來咱們看到了 li 標籤裏面有vue指令 v-for,在這裏它會循環遍歷vue實例的數據 anchorInfo 數組,每次遍歷的變量別名爲 anchor

在上圖能夠看到, ul 標籤下面生成了十個li標籤,正好是咱們 anchorInfo 數組的長度。咱們接着給 li 標籤裏面添加內容。

<li v-for="anchor in anchorInfo">
    <a class="link">
        <div class="live">
            <p>觀看直播 ></p>
        </div>
        <img :src="anchor.userID | getUserImg" class="user">
        <img src="./images/play.png" class="play">
        <p class="add">+1</p>
    </a>
</li>

(補充)這裏給出vue的排序指令代碼:

li 標籤改爲這樣:

<li v-for="anchor in anchorInfo | orderBy supportCntFn">

在vue實例裏面的 method 對象添加:

supportCntFn: function(a, b) {
    return (parseInt(b.supportCnt, 10) - parseInt(a.supportCnt, 10) >= 0);
},

這裏經過parseInt的緣由是後臺傳回來的是字符串類型,若是直接排序的話 2 會比 10 排在前面,顯然不符合咱們的要求。後面繼續。

是否正在直播的DOM元素 .live 和點擊投票的+1動畫的DOM元素 add 咱們暫時不考慮它們,在CSS裏面都默認設置了 display:none。這裏主要看的是用戶的封面圖 .user

<img :src="anchor.userID | getUserImg" class="user">

這裏使用了過濾器 getUserImg (注意這裏是 :src屬性綁定)。因此咱們會在vue實例裏面添加一個新的屬性 filters以及 getUserImg過濾器定義:

filters: {
    getUserImg: function(val) {
        return 'http://a.impingo.me/static/activity/singer/resource/' + val + '.jpg'
    },
},

而咱們當初在和後臺協商的時候,圖片的地址是 domain+userID+.jpg,因此在 getUserImg 過濾器裏面的參數 val 就是咱們傳入的用戶的ID,以後再進行拼湊,返回就行了。

以後在 li 標籤繼續加入下面的部分:

<div class="user-wrapper">
    <div class="name" v-text="anchor.anchorName"></div>
    <div class="num" v-text="anchor.supportCnt"></div>
</div>

這裏應該很明顯就能明白,是輸出了用戶的名字和投票數了。

<template v-if="voteStatus | getVoteStatus anchor">
    <div class="had-btn">
        <p>今日已支持</p>
    </div>
</template>
<template v-else>
    <div class="do-btn">
        <p>支持</p>
    </div>
</template>

咱們繼續在 li 標籤裏面添加了這樣的代碼,template 能夠配合 vue的指令 v-if 一同使用。在這裏你可能稍微講解下 v-if="voteStatus | getVoteStatus anchor" 是來判斷用戶是否已經投票了,已經投票的話顯示 .had-btn 元素,不然顯示 .do-btn 元素,在後面會補充上。

能夠看到咱們大部分的UI界面已經完成了。看看其實寥寥幾十段代碼而已,就把經過jquery來拼錯DOM的繁雜方法完成了。

接下來咱們主要考慮交互的部分了,在這以前咱們先來獲取用戶是否在直播的狀態吧。

var app = new Vue({
    el: '#app',
    data: {
        ...
        livingInfo: [],
        getLiveStatusUrl: "http://a.impingo.me/activity/getLiveStatus",
        ...
    },
    ready: function() {
        ...
        this.getLiveStatus();
        ...
    },
    methods: {
        ...
        getLiveStatus: function() {
            this.$http.jsonp(this.getLiveStatusUrl)
                .then(function(res) {
                    var that = this;
                    var rtnData = res.data;
                    if (rtnData.rtn == 0) {
                        this.$set('livingInfo', rtnData.data);
                    }
                })
                .catch(function(res) {
                    console.info('網絡失敗');
                });
        },
        ...
    },
})

咱們添加了上面的代碼,data裏面的直播信息數組livingInfo和直播信息接口地址getLiveStatusUrl。在ready方法裏面添加了一個新的函數調用this.getLiveStatus();對應的函數定義在methods對象裏面。核心部分在

this.$set('livingInfo', rtnData.data);

咱們和上面同樣,把返回的數組 res.data.rtn表明成功的狀況下,給livingInfo數組賦值res.data.data

看看咱們返回的jsonp數據。咱們主要關注 state 變量,只有值爲 1 的時候表明正在直播,因此咱們如今修改一些HTML結構:

<div class="live" v-show="living | getLiving anchor">
    <p>觀看直播 ></p>
</div>

.live 增長vue指令v-show,只有 livingtrue 的時候,它纔會顯示出來。咱們在下面定義 getLiving 過濾器

getLiving: function(val, anchor) {
    var curUserID = anchor.userID,
        isLiving = false;
    this.livingInfo.forEach(function(living) {
        if (living.createUserID === curUserID) {
            if (living.state == "1") {
                isLiving = true;
            }
        }
    });
    return isLiving;
},

過濾器接收兩個變量,須要過濾的值以及anchor,即對應的用戶。

咱們把用戶的ID賦值給 curUserID 變量,初始化表明是否在直播的變量 isLiving 的值爲false,默認不顯示。

而後咱們使用forEach方法遍歷 livingInfo 數組,而且判斷此刻 living.createUserIDcurUserID 相等的時候,看看它的 state 的屬性,若是爲1的話,isLiving 設置爲真。不然其餘狀況返回 false。(這裏能夠不用 forEach 方法,由於在找到對應的 living 的時候, forEach 並不能退出循環。)

如上圖,如今正在直播的用戶就能顯示出觀看直播這個標籤了。

接下來咱們來獲取是否能夠投票的信息。

var app = new Vue({
    el: '#app',
    data: {
        ...
        queryVoteStatusUrl: "http://a.impingo.me/activity/queryVoteStatus",
        anchorUserID: '',
        todayHadVote: false
        ...
    },
    ready: function() {
        ...
        this.queryVoteStatus();
        ...
    },
    methods: {
        ...
        queryVoteStatus: function() {
            // this.$http.jsonp(this.queryVoteStatusUrl + '?userID=' + selfUserID)
            this.$http.jsonp(this.queryVoteStatusUrl, {
                    params: {
                        'userID': selfUserID
                    }
                })
                .then(function(res) {
                    var rtnData = res.data;
                    if (rtnData.rtn == 0) {
                        this.todayHadVote = false;
                    } else if (rtnData.rtn == 1) {
                        this.todayHadVote = true;
                        this.anchorUserID = rtnData.data.anchorUserID;
                    }
                })
                .catch(function(res) {
                    console.info('網絡失敗');
                });
        },
        ...
    },
    filters: {
        ...
        getVoteStatus: function(val, anchor) {
            if (anchor.userID == this.anchorUserID) {
                // 可支持
                return true;
            } else {
                // 不可支持
                return false;
            }
        }
        ...
    },
});

上面是咱們添加的新代碼。 queryVoteStatusUrl 表明着獲取是否已投票的接口地址(這個地址後面須要加上當前投票用戶的userID,咱們能夠本身在地址後面添加 userID=10003等,userID從10000開始到11000均可以用來測試)。anchorUserID 爲空字符串,後面獲取數據的時候若是已投票,會把投給的那我的的ID賦值給它。 todayHadVote 表明今天是否已經投票了,若是已經投票的話禁止繼續投票。

因此咱們在vue實例的 methods 對象能夠看到 queryVoteStatus 方法,若是 res.data.rtn 爲0的時候,表明今天還能夠投票,進行下面的操做:

this.todayHadVote = true;
this.anchorUserID = rtnData.data.anchorUserID;

最後就是添加的 getVoteStatus 過濾器,以下圖,若是 voteStatus 爲真,今日已支持按鈕會顯示出來,不然顯示支持按鈕

<template v-if="voteStatus | getVoteStatus anchor">
    <div class="had-btn">
        <p>今日已支持</p>
    </div>
</template>
<template v-else>
    <div class="do-btn">
        <p>支持</p>
    </div>
</template>

getVoteStatus 過濾器的代碼以下:

getVoteStatus: function(val, anchor) {
    if (anchor.userID == this.anchorUserID) {
        // 可支持
        return true;
    } else {
        // 不可支持
        return false;
    }
}

只有噹噹前用戶的ID和 data 裏面的 anchorUserID 一致的時候,voteStatus 會返回 true

固然咱們如今都尚未進行操做,因此全部的按鈕都是支持按鈕,咱們能夠在先修改爲下面這樣:本身把 todayHadVote 設置爲 true ,而 anchorUserID 設置一個存在的用戶ID來看效果(而後記得撤銷修改)

if (rtnData.rtn == 0) {
    this.todayHadVote = true;
    this.anchorUserID = 1089536;
} else if (rtnData.rtn == 1) {
    this.todayHadVote = true;
    this.anchorUserID = rtnData.data.anchorUserID;
}

截圖以下:

接下來還有一個小的需求,就是每隔一段時間從新拉取用戶的信息和是否在直播的狀態,添加下面的代碼:

var app = new Vue({
    el: '#app',
    data: {
        ...
        setIntervalGetAnchorInfo: null,
        setIntervalGetLiveStatus: null,
        intervalDuration: 60 * 1000,
        ...
    },
    ready: function() {
        ...
        this.initSetTimeout();
        ...
    },
    methods: {
        ...
        initSetTimeout: function() {
            var that = this;
            setIntervalGetAnchorInfo = setInterval(function() {
                that.getAnchorInfo();
            }, that.intervalDuration);
            setIntervalGetLiveStatus = setInterval(function() {
                that.getLiveStatus();
            }, that.intervalDuration);
        },
        ...
    },
});

獲取用戶信息的定時器 setIntervalGetAnchorInfo 和獲取直播狀態的定時器 setIntervalGetLiveStatus,初始化定時器的 initSetTimeout 方法。

接下來就開始講解交互部分,首先是投票部分。

<div class="do-btn" @click="singerVote(anchor)">
    <p>支持</p>
</div>

給支持按鈕添加一個點擊事件,監聽函數是 singerVote ,把當前用戶當作參數傳入。

var app = new Vue({
    el: '#app',
    data: {
        ....
        singerVoteUrl: "http://a.impingo.me/activity/singerVote",
        ...
    },
    methods: {
        ...
        singerVote: function(anchor) {
            var getUserID = selfUserID,
                getTargetUserID = anchor.userID;

            if (this.todayHadVote) {
                console.info('每日僅支持一次!');
                return;
            }

            this.$http.jsonp(this.singerVoteUrl, {
                    params: {
                        userID: getUserID,
                        targetUserID: getTargetUserID,
                        sessionID: selfSessionID,
                        sessionToken: selfSessionToken,
                        peerID: selfPeerID
                    }
                })
                .then(function(res) {
                    var rtnData = res.data,
                        that = this;
                    if (rtnData.rtn == 0) {
                        // console.info(rtnData.msg);
                        Vue.set(anchor, 'showAdd', true);
                        anchor.supportCnt++;
                        this.anchorUserID = getTargetUserID;
                        this.todayHadVote = true;

                        clearInterval(setIntervalGetAnchorInfo);

                        // 點擊投票,動畫(2秒)之後,從新拉取直播狀態以及直播信息
                        setTimeout(function() {
                            that.getAnchorInfo();
                            that.getLiveStatus();

                            setIntervalGetAnchorInfo = setInterval(function() {
                                that.getAnchorInfo();
                            }, that.intervalDuration);
                        }, 2000);

                    } else if (rtnData.rtn == 2 || rtnData.rtn == 3 || rtnData.rtn == 1) {
                        console.info(rtnData.msg);
                    }
                })
                .catch(function(res) {
                    console.info('網絡失敗');
                });
        },
        ...
    },
});

咱們能夠看到上面是點擊時候的處理。 singerVoteUrl 是投票接口的地址,singerVote 是對應的方法。

一開始看到,若是已經投票了,會反饋 每日僅支持一次! 的提示語,由 this.todayHadVote 判斷。不然,經過 vue-resource 發起請求。

由於上面已經提到不少次了,這裏就不贅述太多,咱們看看主要的部分。

咱們應該還記得:

<p class="add" v-show="anchor.showAdd">+1</p>

這個+1的動畫的元素,點擊投票,成功反饋之後,會進行

Vue.set(anchor, 'showAdd', true);

這個操做,這個時候 .add 元素就會顯示出來了。

anchor.supportCnt++;
this.anchorUserID = getTargetUserID;
this.todayHadVote = true;

以後咱們是本地該用戶的投票數 ++,而後設置用戶今天已投票,以及投票的人的ID

clearInterval(setIntervalGetAnchorInfo);

以後咱們清楚了獲取用戶信息的計時器

setTimeout(function() {
    that.getAnchorInfo();
    that.getLiveStatus();

    setIntervalGetAnchorInfo = setInterval(function() {
        that.getAnchorInfo();
    }, that.intervalDuration);
}, 2000);

並在兩秒(+1動畫結束之後),從新獲取直播信息還有主播信息,而且重啓獲取用戶信息的計時器。這裏主要考慮的是,點擊之後,用戶的票數會改變,排序上可能會改變,這個時候從新從後臺獲取信息,能保證點擊之後數據是最新的,排序也是正確的。而清除計時器的緣由是,在此次交互後咱們已經更新了數據,計時器就應該重置,在規定的 that.intervalDuration 時間之後再從新拉取。

//this.$http.jsonp(this.singerVoteUrl + '?userID=' + getUserID + '&targetUserID=' + getTargetUserID + '&sessionID=' + selfSessionID + '&sessionToken=' + selfSessionToken + '&peerID=' + selfPeerID)

this.$http.jsonp(this.singerVoteUrl, {
    params: {
        userID: getUserID,
        targetUserID: getTargetUserID,
        sessionID: selfSessionID,
        sessionToken: selfSessionToken,
        peerID: selfPeerID
    }
});

另外咱們在這裏看到一竄拼接的地址, vue-resource 應該是能夠傳遞 data 對象來傳遞參數的,試了幾回不知道爲何都不行,待改善。

更新:vue-resource傳參能夠經過上面的方法。 而後這個地方可能會報錯,由於後臺須要 sessionIDsessionToken

?userID=10003&peerID=45C7781DE9BF&sessionID=67056f7abd062d4dea&&sessionToken=3df4ce5d23

能夠按照上面這樣在url地址加上,而後再發送請求。

<div class="name" v-text="anchor.anchorName" @click="jumpProfile(anchor.userID)"></div>

另外也有一個點擊用戶名跳轉到他我的主頁的需求,咱們簡單的增長一個方法就行了

jumpProfile: function(userID) {
    console.log(userID);
    if (window.pingo_js) {
        window.pingo_js.jumpPage('profile://' + userID);
    }
},

這裏的 window.pingo_js 不用考慮太多,是公司APP的接口,後面也有這樣的代碼,可無視。

<a class="link" @click="jumpVideo(anchor)">
    <div class="live" v-show="living | getLiving anchor">
        <p>觀看直播 ></p>
    </div>
    <img :src="anchor.userID | getUserImg" class="user">
    <img src="./images/play.png" class="play">
    <p class="add" v-show="anchor.showAdd">+1</p>
</a>

咱們這裏再給 .link 添加了一個 jumpVideo 的點擊事件綁定。

jumpVideo: function(anchor) {
    var curUserID = anchor.userID;
    window.location.href = 'http://api.impingo.me/static/singer/preselection-live.html?userID=' + curUserID; // 視頻地址
    return;
},

就只是簡單的跳轉到咱們準備好的視頻播放地址,傳入用戶的ID就行了。

<div class="live" v-show="living | getLiving anchor" @click.stop="jumpLive(anchor)">
    <p>觀看直播 ></p>
</div>

而正在直播的用戶,點擊觀看直播的時候,咱們綁定了 jumpLive 事件。這裏給 @click 加了一個修飾符 .stop ,即禁止冒泡,反正冒泡到父元素的 jumpVideo 點擊事件函數。

jumpLive: function(anchor) {
    var curUserID = anchor.userID,
        curRoomID;
    this.livingInfo.forEach(function(living) {
        if (living.createUserID === curUserID) {
            if (living.state == "1") {
                curRoomID = living.roomID;
            }
        }
    });
    window.location.href = 'http://api.impingo.me/miniSite/livePage?liveID=' + curRoomID;
}

而裏面也是簡單地循環遍歷 livingInfo 數組來匹配對應的用戶,找出它直播間的房號,跳轉到直播頁面(這裏也有一個跳轉到APP直播間的方法,省略掉了,下降理解成本和代碼量)。

大功告成。

感受須要改善的地方有:

  • livingInfo 數組和 anchorInfo 數組能夠經過 computed 屬性計算合成一個大的數組,那麼不少的過濾器還有 forEach 遍歷就能夠省略掉了

  • 能夠把整個 ul 下的部分作成一個組件

  • 文章可能描述的很囉嗦

所有代碼:

源碼地址

guide.html:

<!DOCTYPE html>
<html>

<head>
    <title>vue guide</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
    <meta content="telephone=no" name="format-detection" />
    <meta content="email=no" name="format-detection" />
    <link rel="stylesheet" href="./css/guide.css" />
    <script src="http://7xnv74.com1.z0.glb.clouddn.com/static/lib/flexible/flexible.js"></script>
</head>

<body>
    <div class="container" id="app">
        <div class="radio-wrapper">
            <ul class="list clearfix" v-cloak>
                <li v-for="anchor in anchorInfo">
                    <a class="link" @click="jumpVideo(anchor)">
                        <div class="live" v-show="living | getLiving anchor" @click.stop="jumpLive(anchor)">
                            <p>觀看直播 ></p>
                        </div>
                        <img :src="anchor.userID | getUserImg" class="user">
                        <img src="./images/play.png" class="play">
                        <p class="add" v-show="anchor.showAdd">+1</p>
                    </a>
                    <div class="user-wrapper">
                        <div class="name" v-text="anchor.anchorName" @click="jumpProfile(anchor.userID)"></div>
                        <div class="num" v-text="anchor.supportCnt"></div>
                    </div>
                    <template v-if="voteStatus | getVoteStatus anchor">
                        <div class="had-btn">
                            <p>今日已支持</p>
                        </div>
                    </template>
                    <template v-else>
                        <div class="do-btn" @click="singerVote(anchor)">
                            <p>支持</p>
                        </div>
                    </template>
                </li>
            </ul>
        </div>
    </div>
    <script src="http://7xnv74.com1.z0.glb.clouddn.com/static/lib/fastclick/fastclick.min.js"></script>
    <script src="./js/vue.min.js"></script>
    <script src="./js/vue-resource.min.js"></script>
    <script src="./js/guide.js"></script>
</body>

</html>

guide.js

var lib = {
    urlParams: function(url) {
        var urlParamsList = {};
        var params = url.search.replace(/^\?/, "").split('&'); //分開成各個不一樣的對像,去掉'&'
        for (var i = 0; i < params.length; i++) {
            var param = params[i];
            var temp = param.split("=");
            urlParamsList[temp[0]] = decodeURI(temp[1]);
        }
        return urlParamsList;
    }
};

window.onload = function() {

    var attachFastClick = Origami.fastclick;
    attachFastClick(document.body);

    var windowLocation = window.location,
        selfUserID = lib.urlParams(windowLocation)['userID'],
        selfSessionID = lib.urlParams(windowLocation)['sessionID'],
        selfSessionToken = lib.urlParams(windowLocation)['sessionToken'],
        selfPeerID = lib.urlParams(windowLocation)['peerID'];

    var app = new Vue({
        el: '#app',
        data: {
            anchorInfo: [],
            livingInfo: [],
            getAnchorInfoUrl: "http://a.impingo.me/activity/getAnchorInfo",
            getLiveStatusUrl: "http://a.impingo.me/activity/getLiveStatus",
            queryVoteStatusUrl: "http://a.impingo.me/activity/queryVoteStatus",
            singerVoteUrl: "http://a.impingo.me/activity/singerVote",
            anchorUserID: '',
            todayHadVote: false,
            setIntervalGetLiveStatus: null,
            setIntervalGetAnchorInfo: null,
            intervalDuration: 60 * 1000,
        },
        ready: function() {
            this.getAnchorInfo();
            this.getLiveStatus();
            this.queryVoteStatus();
            this.initSetTimeout();
        },
        methods: {
            getAnchorInfo: function() {
                this.$http.jsonp(this.getAnchorInfoUrl)
                    .then(function(res) {
                        console.log(res);
                        var rtnData = res.data;
                        if (rtnData.rtn == 0) {
                            this.$set('anchorInfo', rtnData.data);
                        }
                    })
                    .catch(function(res) {
                        console.info('網絡失敗');
                    });
            },
            getLiveStatus: function() {
                this.$http.jsonp(this.getLiveStatusUrl)
                    .then(function(res) {
                        var that = this;
                        var rtnData = res.data;
                        if (rtnData.rtn == 0) {
                            this.$set('livingInfo', rtnData.data);
                        }
                    })
                    .catch(function(res) {
                        console.info('網絡失敗');
                    });
            },
            queryVoteStatus: function() {
                // this.$http.jsonp(this.queryVoteStatusUrl + '?userID=' + selfUserID)
                this.$http.jsonp(this.queryVoteStatusUrl, {
                        params: {
                            'userID': selfUserID
                        }
                    })
                    .then(function(res) {
                        var rtnData = res.data;
                        if (rtnData.rtn == 0) {
                            this.todayHadVote = false;
                        } else if (rtnData.rtn == 1) {
                            this.todayHadVote = true;
                            this.anchorUserID = rtnData.data.anchorUserID;
                        }
                    })
                    .catch(function(res) {
                        console.info('網絡失敗');
                    });
            },
            initSetTimeout: function() {
                var that = this;
                setIntervalGetAnchorInfo = setInterval(function() {
                    that.getAnchorInfo();
                }, that.intervalDuration);
                setIntervalGetLiveStatus = setInterval(function() {
                    that.getLiveStatus();
                }, that.intervalDuration);
            },
            singerVote: function(anchor) {
                var getUserID = selfUserID,
                    getTargetUserID = anchor.userID;

                if (this.todayHadVote) {
                    console.info('每日僅支持一次!');
                    return;
                }

                this.$http.jsonp(this.singerVoteUrl, {
                        params: {
                            userID: getUserID,
                            targetUserID: getTargetUserID,
                            sessionID: selfSessionID,
                            sessionToken: selfSessionToken,
                            peerID: selfPeerID
                        }
                    })
                    .then(function(res) {
                        var rtnData = res.data,
                            that = this;
                        if (rtnData.rtn == 0) {
                            // console.info(rtnData.msg);
                            Vue.set(anchor, 'showAdd', true);
                            anchor.supportCnt++;
                            this.anchorUserID = getTargetUserID;
                            this.todayHadVote = true;

                            clearInterval(setIntervalGetAnchorInfo);

                            // 點擊投票,動畫(2秒)之後,從新拉取直播狀態以及直播信息
                            setTimeout(function() {
                                that.getAnchorInfo();
                                that.getLiveStatus();

                                setIntervalGetAnchorInfo = setInterval(function() {
                                    that.getAnchorInfo();
                                }, that.intervalDuration);
                            }, 2000);

                        } else if (rtnData.rtn == 2 || rtnData.rtn == 3 || rtnData.rtn == 1) {
                            console.info(rtnData.msg);
                        }
                    })
                    .catch(function(res) {
                        console.info('網絡失敗');
                    });
            },
            jumpProfile: function(userID) {
                console.log(userID);
                if (window.pingo_js) {
                    window.pingo_js.jumpPage('profile://' + userID);
                }
            },
            jumpVideo: function(anchor) {
                var curUserID = anchor.userID;
                window.location.href = 'http://api.impingo.me/static/singer/preselection-live.html?userID=' + curUserID; // 視頻地址
                return;
            },
            jumpLive: function(anchor) {
                var curUserID = anchor.userID,
                    curRoomID;
                this.livingInfo.forEach(function(living) {
                    if (living.createUserID === curUserID) {
                        if (living.state == "1") {
                            curRoomID = living.roomID;
                        }
                    }
                });
                window.location.href = 'http://api.impingo.me/miniSite/livePage?liveID=' + curRoomID;
            }
        },
        filters: {
            getUserImg: function(val) {
                return 'http://a.impingo.me/static/activity/singer/resource/' + val + '.jpg'
            },
            getLiving: function(val, anchor) {
                var curUserID = anchor.userID,
                    isLiving = false;
                this.livingInfo.forEach(function(living) {
                    if (living.createUserID === curUserID) {
                        if (living.state == "1") {
                            isLiving = true;
                        }
                    }
                });
                return isLiving;
            },
            getVoteStatus: function(val, anchor) {
                if (anchor.userID == this.anchorUserID) {
                    // 可支持
                    return true;
                } else {
                    // 不可支持
                    return false;
                }
            },
        },
    });
}

guide.less

@import (inline) './normalize.css';
body {
    background-color: #010017;
}

.container {
    user-select: none;
    font-family: 'Microsoft YaHei', sans-serif;
    position: relative;
    min-width: 320px;
    max-width: 750px;
    margin: 0 auto;
    font-size: 0.32rem;
}

[v-cloak] {
    display: none;
}

// 設計稿是 750px
// 1rem = 75px
@base: 75rem;
.demo {
    text-align: center;
    .btn {
        width: 560 / @base;
    }
}

.radio-wrapper {
    .list {
        padding-left: 18/@base;
        padding-right: 18/@base;
        padding-top: 35/@base;
        li {
            background-color: #fff;
            width: 346/@base;
            height: 488/@base;
            position: relative;
            margin-bottom: 20/@base;
            float: left;
            display: table;
            &:nth-child(odd) {
                // margin-right: 10/@base;
            }
            &:nth-child(even) {
                float: right;
            }
            .live {
                position: absolute;
                background-color: #2aa2fe;
                width: 150/@base;
                height: 50/@base;
                border-top-right-radius: 100px;
                border-bottom-right-radius: 100px;
                left: -11/@base;
                top: 29/@base;
                z-index: 99;
                display: table;
                p {
                    color: #fff;
                    font-size: 24/@base;
                    text-align: center;
                    vertical-align: middle;
                    display: table-cell;
                }
            }
            .link {
                display: block;
                width: 324/@base;
                height: 324/@base;
                position: absolute;
                left: 11/@base;
                right: 11/@base;
                top: 10/@base;
            }
            .user {
                width: 324/@base;
                display: block;
            }
            .play {
                width: 60/@base;
                position: absolute;
                left: 30/@base;
                top: 250/@base;
            }
            .add {
                position: absolute;
                font-size: 30/@base;
                font-weight: bold;
                color: #f919b6;
                z-index: 99;
                right: 30/@base;
                top: 310/@base;
                -webkit-animation: fadeOutUp 2s .2s ease both;
            }
            @-webkit-keyframes fadeOutUp {
                0% {
                    opacity: 1;
                    -webkit-transform: translateY(0)
                }
                30% {
                    opacity: 0.7;
                    font-size: 40/@base;
                    -webkit-transform: translateY(-15px)
                }
                100% {
                    opacity: 0;
                    -webkit-transform: translateY(-30px)
                }
            }
            .user-wrapper {
                position: absolute;
                left: 11/@base;
                top: 350/@base;
                width: 320/@base;
                .name {
                    color: #333;
                    font-size: 26/@base;
                    display: inline-block;
                    width: 150/@base;
                    text-overflow: ellipsis;
                    overflow: hidden;
                    white-space: nowrap;
                }
                .num {
                    color: #f919b6;
                    font-size: 26/@base;
                    display: inline-block;
                    float: right;
                    // margin-left: 200/@base;
                }
            }
            .do-btn {
                background-color: #f919b6;
                text-align: center;
                border-radius: 15/@base;
                width: 306/@base;
                height: 70/@base;
                bottom: 20/@base;
                left: 20/@base;
                position: absolute;
                display: table;
                p {
                    display: table-cell;
                    vertical-align: middle;
                    font-size: 30/@base;
                    color: #fff;
                }
            }
            .had-btn {
                background-color: #ffb9e8;
                text-align: center;
                border-radius: 15/@base;
                width: 306/@base;
                height: 70/@base;
                bottom: 20/@base;
                left: 20/@base;
                position: absolute;
                display: table;
                p {
                    display: table-cell;
                    vertical-align: middle;
                    font-size: 30/@base;
                    color: rgba(255, 255, 255, 0.6);
                }
            }
        }
    }
}
相關文章
相關標籤/搜索