博客地址:http://cody1991.github.io/vue/2016/08/30/a-simple-vue-guide.htmlcss
更新 (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,只有 living
爲 true
的時候,它纔會顯示出來。咱們在下面定義 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.createUserID
和 curUserID
相等的時候,看看它的 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傳參能夠經過上面的方法。 而後這個地方可能會報錯,由於後臺須要 sessionID
和 sessionToken
?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); } } } } }