https://segmentfault.com/a/1190000006776243?utm_source=tuicool&utm_medium=referral

感觉需要改善的地方有:

  • (更新代码)livingInfo 数组和 anchorInfo 数组可以通过 computed 属性计算合成一个大的数组,那么很多的过滤器还有 forEach 遍历就可以省略掉了

  • 可以把整个 ul 下的部分做成一个组件

  • 文章可能描述的很啰嗦


公司有一个项目,其中一部分的截图如下:

主要需求如下:

  • 需要拉取十个人的信息,包括封面图,名字,票数,以及对应用户是否进行了投票等信息,以及根据票数排序

  • 正在直播的人在右上角会有一个提示

  • 点击支持的时候,需要反馈给后台,并且前端这边会有+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实践吧。

源码地址

  1. <div class="container" id="app">
  2. </div>
  3. var app = new Vue({
  4. el: '#app'
  5. });

上面是最简单的 vue 实例初始化。

接下来我们继续构建我们的应用

在未使用 vue.js 之前,我们简单地使用HTML和CSS重构我们的项目:

  1. <div class="container" id="app">
  2. <div class="radio-wrapper">
  3. <ul class="list clearfix">
  4. <li>
  5. <a class="link">
  6. <div class="live">
  7. <p>观看直播 ></p>
  8. </div>
  9. <img src="http://a.impingo.me/static/activity/singer/resource/1616312.jpg" class="user">
  10. <img src="./images/play.png" class="play">
  11. <p class="add">+1</p>
  12. </a>
  13. <div class="user-wrapper">
  14. <div class="name">凌兒</div>
  15. <div class="num">3280</div>
  16. </div>
  17. <div class="do-btn">
  18. <p>支持</p>
  19. </div>
  20. </li>
  21. <li>
  22. <a class="link">
  23. <div class="live">
  24. <p>观看直播 ></p>
  25. </div>
  26. <img src="http://a.impingo.me/static/activity/singer/resource/1616312.jpg" class="user">
  27. <img src="./images/play.png" class="play">
  28. <p class="add">+1</p>
  29. </a>
  30. <div class="user-wrapper">
  31. <div class="name">凌兒</div>
  32. <div class="num">3280</div>
  33. </div>
  34. <div class="do-btn">
  35. <p>支持</p>
  36. </div>
  37. </li>
  38. </ul>
  39. </div>
  40. </div>

大体上的HTML结构就是这样,配合CSS样式,可以得到下面的输出结果:

当然现在还都是静态数据。

在 ul 里面的 li ,就需要我们使用 v-for 指令来进行循环输出了。下面再继续说明。

首先来看看我们一开始的 js 部分的代码:

  1. var lib = {
  2. urlParams: function(url) {
  3. var urlParamsList = {};
  4. var params = url.search.replace(/^\?/, "").split('&'); //分开成各个不同的对像,去掉'&'
  5. for (var i = 0; i < params.length; i++) {
  6. var param = params[i];
  7. var temp = param.split("=");
  8. urlParamsList[temp[0]] = decodeURI(temp[1]);
  9. }
  10. return urlParamsList;
  11. }
  12. };
  13. window.onload = function() {
  14. var attachFastClick = Origami.fastclick;
  15. attachFastClick(document.body);
  16. var windowLocation = window.location,
  17. selfUserID = lib.urlParams(windowLocation)['userID'],
  18. selfSessionID = lib.urlParams(windowLocation)['sessionID'],
  19. selfSessionToken = lib.urlParams(windowLocation)['sessionToken'],
  20. selfPeerID = lib.urlParams(windowLocation)['peerID'];
  21. var app = new Vue({
  22. el: '#app',
  23. data: {
  24. anchorInfo: [],
  25. getAnchorInfoUrl: "http://a.impingo.me/activity/getAnchorInfo",
  26. },
  27. ready: function() {
  28. this.getAnchorInfo();
  29. },
  30. methods: {
  31. getAnchorInfo: function() {
  32. this.$http.jsonp(this.getAnchorInfoUrl)
  33. .then(function(res) {
  34. var rtnData = res.data;
  35. if (rtnData.rtn == 0) {
  36. this.$set('anchorInfo', rtnData.data);
  37. }
  38. })
  39. .catch(function(res) {
  40. console.info('网络失败');
  41. });
  42. }
  43. }
  44. })
  45. }

lib 对象主要放着一些基础的方法或者变量,在这里只有一个解析页面地址参数的函数 urlParams ,因为后面我们需要通过页面地址url获取投票用户的userID,即后面看到的

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

selfSessionID,selfSessionToken,selfPeerID不用在意太多,到时候url没有传入这几个也没关系。

而 window.onload 开头的这段:

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

引入了 fastclick,消除手机上点击的300ms延时。

之后就是我们上面提到的vue实例了。

我们给实例添加了新的属性 data ,它是一个对象,这里是vue实例存放数据的地方。初始化用户信息 anchorInfo 为空数组,以及用户信息的接口地址 getAnchorInfoUrl 的值为 http://a.impingo.me/activity/getAnchorInfo 。

然后就是添加了新的属性 ready ,它是一个函数,在vue实例初始化完成的时候会调用这个方法。我们看看这个方法下的代码:

  1. this.getAnchorInfo();

this 指向vue实例,调用 getAnchorInfo() 方法。

接着往下看,我们看到一个新的属性 methods ,它是一个对象,放着我们vue实例的所有方法。在这之下我们定义了 getAnchorInfo() 方法。

  1. getAnchorInfo: function() {
  2. this.$http.jsonp(this.getAnchorInfoUrl)
  3. .then(function(res) {
  4. var rtnData = res.data;
  5. if (rtnData.rtn == 0) {
  6. this.$set('anchorInfo', rtnData.data);
  7. }
  8. })
  9. .catch(function(res) {
  10. console.info('网络失败');
  11. });
  12. }

vue-resource 的使用可以看看这里,我们在这里使用 jsonp 方法请求了 getAnchorInfoUrl 地址的接口,如果请求成功的话,then(function(res)){} ,我们看看 res 的数据结构

(补充)vue-resource 的 jsonp 基本写法是(可以参看官方文档 HTTP Requests/Response):

  1. this.$http.jsonp(url,{
  2. params: {
  3. 'someKey': someValue
  4. }
  5. })
  6. // this 是 vue 实例
  7. // url是请求的地址 params是请求的附带参数
  8. .then(function(res){
  9. // 后台成功返回数据的时候
  10. // res 是返回的数据
  11. })
  12. .catch(function(res){
  13. // 后台响应出错的时候
  14. });

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 标签开始修改。

  1. <ul class="list clearfix" v-cloak>
  2. <li v-for="anchor in anchorInfo">
  3. </li>
  4. </ul>

在这里我们可以看到给 ul 标签加了一个v-cloak,这个是vue实例的DOM结构渲染完成以后,会去掉的一个类。因为我们经常在vue实例还没渲染完成的时候会看到一些比如 {{someStr}} 这样的绑定属性,我们在CSS里面添加

  1. [v-cloak] {
  2. display: none;
  3. }

那么在vue实例的DOM还没渲染完成的时候,就会被隐藏起来了。

接下来我们看到了 li 标签里面有vue指令 v-for,在这里它会循环遍历vue实例的数据 anchorInfo 数组,每次遍历的变量别名为 anchor

在上图可以看到, ul 标签下面生成了十个li标签,正好是我们 anchorInfo 数组的长度。我们接着给 li 标签里面添加内容。

  1. <li v-for="anchor in anchorInfo">
  2. <a class="link">
  3. <div class="live">
  4. <p>观看直播 ></p>
  5. </div>
  6. <img :src="anchor.userID | getUserImg" class="user">
  7. <img src="./images/play.png" class="play">
  8. <p class="add">+1</p>
  9. </a>
  10. </li>

(补充)这里给出vue的排序指令代码:

li 标签改成这样:

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

在vue实例里面的 method 对象添加:

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

这里通过parseInt的原因是后台传回来的是字符串类型,如果直接排序的话 2 会比 10 排在前面,显然不符合我们的要求。后面继续。

是否正在直播的DOM元素 .live 和点击投票的+1动画的DOM元素 add 我们暂时不考虑它们,在CSS里面都默认设置了 display:none。这里主要看的是用户的封面图 .user

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

这里使用了过滤器 getUserImg (注意这里是 :src属性绑定)。所以我们会在vue实例里面添加一个新的属性 filters以及 getUserImg过滤器定义:

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

而我们当初在和后台协商的时候,图片的地址是 domain+userID+.jpg,所以在 getUserImg 过滤器里面的参数 val 就是我们传入的用户的ID,之后再进行拼凑,返回就好了。

之后在 li 标签继续加入下面的部分:

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

这里应该很明显就能明白,是输出了用户的名字和投票数了。

  1. <template v-if="voteStatus | getVoteStatus anchor">
  2. <div class="had-btn">
  3. <p>今日已支持</p>
  4. </div>
  5. </template>
  6. <template v-else>
  7. <div class="do-btn">
  8. <p>支持</p>
  9. </div>
  10. </template>

我们继续在 li 标签里面添加了这样的代码,template 可以配合 vue的指令 v-if 一同使用。在这里你可能稍微讲解下 v-if="voteStatus | getVoteStatus anchor" 是来判断用户是否已经投票了,已经投票的话显示 .had-btn 元素,否则显示 .do-btn元素,在后面会补充上。

可以看到我们大部分的UI界面已经完成了。看看其实寥寥几十段代码而已,就把通过jquery来拼错DOM的繁杂方法完成了。

接下来我们主要考虑交互的部分了,在这之前我们先来获取用户是否在直播的状态吧。

  1. var app = new Vue({
  2. el: '#app',
  3. data: {
  4. ...
  5. livingInfo: [],
  6. getLiveStatusUrl: "http://a.impingo.me/activity/getLiveStatus",
  7. ...
  8. },
  9. ready: function() {
  10. ...
  11. this.getLiveStatus();
  12. ...
  13. },
  14. methods: {
  15. ...
  16. getLiveStatus: function() {
  17. this.$http.jsonp(this.getLiveStatusUrl)
  18. .then(function(res) {
  19. var that = this;
  20. var rtnData = res.data;
  21. if (rtnData.rtn == 0) {
  22. this.$set('livingInfo', rtnData.data);
  23. }
  24. })
  25. .catch(function(res) {
  26. console.info('网络失败');
  27. });
  28. },
  29. ...
  30. },
  31. })

我们添加了上面的代码,data里面的直播信息数组livingInfo和直播信息接口地址getLiveStatusUrl。在ready方法里面添加了一个新的函数调用this.getLiveStatus();对应的函数定义在methods对象里面。核心部分在

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

我们和上面一样,把返回的数组 res.data.rtn代表成功的情况下,给livingInfo数组赋值res.data.data

看看我们返回的jsonp数据。我们主要关注 state 变量,只有值为 1 的时候代表正在直播,所以我们现在修改一些HTML结构:

  1. <div class="live" v-show="living | getLiving anchor">
  2. <p>观看直播 ></p>
  3. </div>

给 .live 增加vue指令v-show,只有 living 为 true 的时候,它才会显示出来。我们在下面定义 getLiving 过滤器

  1. getLiving: function(val, anchor) {
  2. var curUserID = anchor.userID,
  3. isLiving = false;
  4. this.livingInfo.forEach(function(living) {
  5. if (living.createUserID === curUserID) {
  6. if (living.state == "1") {
  7. isLiving = true;
  8. }
  9. }
  10. });
  11. return isLiving;
  12. },

过滤器接收两个变量,需要过滤的值以及anchor,即对应的用户。

我们把用户的ID赋值给 curUserID 变量,初始化代表是否在直播的变量 isLiving 的值为false,默认不显示。

然后我们使用forEach方法遍历 livingInfo 数组,并且判断此刻 living.createUserID 和 curUserID 相等的时候,看看它的 state 的属性,如果为1的话,isLiving 设置为真。否则其他情况返回 false。(这里可以不用 forEach 方法,因为在找到对应的 living 的时候, forEach 并不能退出循环。)

如上图,现在正在直播的用户就能显示出观看直播这个标签了。

接下来我们来获取是否可以投票的信息。

  1. var app = new Vue({
  2. el: '#app',
  3. data: {
  4. ...
  5. queryVoteStatusUrl: "http://a.impingo.me/activity/queryVoteStatus",
  6. anchorUserID: '',
  7. todayHadVote: false
  8. ...
  9. },
  10. ready: function() {
  11. ...
  12. this.queryVoteStatus();
  13. ...
  14. },
  15. methods: {
  16. ...
  17. queryVoteStatus: function() {
  18. // this.$http.jsonp(this.queryVoteStatusUrl + '?userID=' + selfUserID)
  19. this.$http.jsonp(this.queryVoteStatusUrl, {
  20. params: {
  21. 'userID': selfUserID
  22. }
  23. })
  24. .then(function(res) {
  25. var rtnData = res.data;
  26. if (rtnData.rtn == 0) {
  27. this.todayHadVote = false;
  28. } else if (rtnData.rtn == 1) {
  29. this.todayHadVote = true;
  30. this.anchorUserID = rtnData.data.anchorUserID;
  31. }
  32. })
  33. .catch(function(res) {
  34. console.info('网络失败');
  35. });
  36. },
  37. ...
  38. },
  39. filters: {
  40. ...
  41. getVoteStatus: function(val, anchor) {
  42. if (anchor.userID == this.anchorUserID) {
  43. // 可支持
  44. return true;
  45. } else {
  46. // 不可支持
  47. return false;
  48. }
  49. }
  50. ...
  51. },
  52. });

上面是我们添加的新代码。 queryVoteStatusUrl 代表着获取是否已投票的接口地址(这个地址后面需要加上当前投票用户的userID,我们可以自己在地址后面添加 userID=10003等,userID从10000开始到11000都可以用来测试)。anchorUserID 为空字符串,后面获取数据的时候如果已投票,会把投给的那个人的ID赋值给它。 todayHadVote 代表今天是否已经投票了,如果已经投票的话禁止继续投票。

所以我们在vue实例的 methods 对象可以看到 queryVoteStatus 方法,如果 res.data.rtn 为0的时候,代表今天还可以投票,进行下面的操作:

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

最后就是添加的 getVoteStatus 过滤器,如下图,如果 voteStatus 为真,今日已支持按钮会显示出来,否则显示支持按钮

  1. <template v-if="voteStatus | getVoteStatus anchor">
  2. <div class="had-btn">
  3. <p>今日已支持</p>
  4. </div>
  5. </template>
  6. <template v-else>
  7. <div class="do-btn">
  8. <p>支持</p>
  9. </div>
  10. </template>

getVoteStatus 过滤器的代码如下:

  1. getVoteStatus: function(val, anchor) {
  2. if (anchor.userID == this.anchorUserID) {
  3. // 可支持
  4. return true;
  5. } else {
  6. // 不可支持
  7. return false;
  8. }
  9. }

只有当当前用户的ID和 data 里面的 anchorUserID 一致的时候,voteStatus 会返回 true

当然我们现在都还没有进行操作,所以所有的按钮都是支持按钮,我们可以在先修改成下面这样:自己把 todayHadVote 设置为 true ,而 anchorUserID 设置一个存在的用户ID来看效果(然后记得撤销修改)

  1. if (rtnData.rtn == 0) {
  2. this.todayHadVote = true;
  3. this.anchorUserID = 1089536;
  4. } else if (rtnData.rtn == 1) {
  5. this.todayHadVote = true;
  6. this.anchorUserID = rtnData.data.anchorUserID;
  7. }

截图如下:

接下来还有一个小的需求,就是每隔一段时间重新拉取用户的信息和是否在直播的状态,添加下面的代码:

  1. var app = new Vue({
  2. el: '#app',
  3. data: {
  4. ...
  5. setIntervalGetAnchorInfo: null,
  6. setIntervalGetLiveStatus: null,
  7. intervalDuration: 60 * 1000,
  8. ...
  9. },
  10. ready: function() {
  11. ...
  12. this.initSetTimeout();
  13. ...
  14. },
  15. methods: {
  16. ...
  17. initSetTimeout: function() {
  18. var that = this;
  19. setIntervalGetAnchorInfo = setInterval(function() {
  20. that.getAnchorInfo();
  21. }, that.intervalDuration);
  22. setIntervalGetLiveStatus = setInterval(function() {
  23. that.getLiveStatus();
  24. }, that.intervalDuration);
  25. },
  26. ...
  27. },
  28. });

获取用户信息的定时器 setIntervalGetAnchorInfo 和获取直播状态的定时器 setIntervalGetLiveStatus,初始化定时器的 initSetTimeout 方法。

接下来就开始讲解交互部分,首先是投票部分。

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

给支持按钮添加一个点击事件,监听函数是 singerVote ,把当前用户当做参数传入。

  1. var app = new Vue({
  2. el: '#app',
  3. data: {
  4. ....
  5. singerVoteUrl: "http://a.impingo.me/activity/singerVote",
  6. ...
  7. },
  8. methods: {
  9. ...
  10. singerVote: function(anchor) {
  11. var getUserID = selfUserID,
  12. getTargetUserID = anchor.userID;
  13. if (this.todayHadVote) {
  14. console.info('每日仅支持一次!');
  15. return;
  16. }
  17. this.$http.jsonp(this.singerVoteUrl, {
  18. params: {
  19. userID: getUserID,
  20. targetUserID: getTargetUserID,
  21. sessionID: selfSessionID,
  22. sessionToken: selfSessionToken,
  23. peerID: selfPeerID
  24. }
  25. })
  26. .then(function(res) {
  27. var rtnData = res.data,
  28. that = this;
  29. if (rtnData.rtn == 0) {
  30. // console.info(rtnData.msg);
  31. Vue.set(anchor, 'showAdd', true);
  32. anchor.supportCnt++;
  33. this.anchorUserID = getTargetUserID;
  34. this.todayHadVote = true;
  35. clearInterval(setIntervalGetAnchorInfo);
  36. // 点击投票,动画(2秒)以后,重新拉取直播状态以及直播信息
  37. setTimeout(function() {
  38. that.getAnchorInfo();
  39. that.getLiveStatus();
  40. setIntervalGetAnchorInfo = setInterval(function() {
  41. that.getAnchorInfo();
  42. }, that.intervalDuration);
  43. }, 2000);
  44. } else if (rtnData.rtn == 2 || rtnData.rtn == 3 || rtnData.rtn == 1) {
  45. console.info(rtnData.msg);
  46. }
  47. })
  48. .catch(function(res) {
  49. console.info('网络失败');
  50. });
  51. },
  52. ...
  53. },
  54. });

我们可以看到上面是点击时候的处理。 singerVoteUrl 是投票接口的地址,singerVote 是对应的方法。

一开始看到,如果已经投票了,会反馈 每日仅支持一次! 的提示语,由 this.todayHadVote 判断。否则,通过 vue-resource 发起请求。

因为上面已经提到很多次了,这里就不赘述太多,我们看看主要的部分。

我们应该还记得:

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

这个+1的动画的元素,点击投票,成功反馈以后,会进行

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

这个操作,这个时候 .add 元素就会显示出来了。

  1. anchor.supportCnt++;
  2. this.anchorUserID = getTargetUserID;
  3. this.todayHadVote = true;

之后我们是本地该用户的投票数 ++,然后设置用户今天已投票,以及投票的人的ID

  1. clearInterval(setIntervalGetAnchorInfo);

之后我们清楚了获取用户信息的计时器

  1. setTimeout(function() {
  2. that.getAnchorInfo();
  3. that.getLiveStatus();
  4. setIntervalGetAnchorInfo = setInterval(function() {
  5. that.getAnchorInfo();
  6. }, that.intervalDuration);
  7. }, 2000);

并在两秒(+1动画结束以后),重新获取直播信息还有主播信息,并且重启获取用户信息的计时器。这里主要考虑的是,点击以后,用户的票数会改变,排序上可能会改变,这个时候重新从后台获取信息,能保证点击以后数据是最新的,排序也是正确的。而清除计时器的原因是,在这次交互后我们已经更新了数据,计时器就应该重置,在规定的 that.intervalDuration 时间以后再重新拉取。

  1. //this.$http.jsonp(this.singerVoteUrl + '?userID=' + getUserID + '&targetUserID=' + getTargetUserID + '&sessionID=' + selfSessionID + '&sessionToken=' + selfSessionToken + '&peerID=' + selfPeerID)
  2. this.$http.jsonp(this.singerVoteUrl, {
  3. params: {
  4. userID: getUserID,
  5. targetUserID: getTargetUserID,
  6. sessionID: selfSessionID,
  7. sessionToken: selfSessionToken,
  8. peerID: selfPeerID
  9. }
  10. });

另外我们在这里看到一窜拼接的地址, vue-resource 应该是可以传递 data 对象来传递参数的,试了几次不知道为什么都不行,待改善。

更新:vue-resource传参可以通过上面的方法。 然后这个地方可能会报错,因为后台需要 sessionID 和 sessionToken

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

可以按照上面这样在url地址加上,然后再发送请求。

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

另外也有一个点击用户名跳转到他个人主页的需求,我们简单的增加一个方法就好了

  1. jumpProfile: function(userID) {
  2. console.log(userID);
  3. if (window.pingo_js) {
  4. window.pingo_js.jumpPage('profile://' + userID);
  5. }
  6. },

这里的 window.pingo_js 不用考虑太多,是公司APP的接口,后面也有这样的代码,可无视。

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

我们这里再给 .link 添加了一个 jumpVideo 的点击事件绑定。

  1. jumpVideo: function(anchor) {
  2. var curUserID = anchor.userID;
  3. window.location.href = 'http://api.impingo.me/static/singer/preselection-live.html?userID=' + curUserID; // 视频地址
  4. return;
  5. },

就只是简单的跳转到我们准备好的视频播放地址,传入用户的ID就好了。

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

而正在直播的用户,点击观看直播的时候,我们绑定了 jumpLive 事件。这里给 @click 加了一个修饰符 .stop ,即禁止冒泡,反正冒泡到父元素的 jumpVideo 点击事件函数。

  1. jumpLive: function(anchor) {
  2. var curUserID = anchor.userID,
  3. curRoomID
  4. this.livingInfo.forEach(function(living) {
  5. if (living.createUserID === curUserID) {
  6. if (living.state == "1") {
  7. curRoomID = living.roomID;
  8. }
  9. }
  10. });
  11. window.location.href = 'http://api.impingo.me/miniSite/livePage?liveID=' + curRoomID;
  12. }

而里面也是简单地循环遍历 livingInfo 数组来匹配对应的用户,找出它直播间的房号,跳转到直播页面(这里也有一个跳转到APP直播间的方法,省略掉了,降低理解成本和代码量)。

大功告成。

感觉需要改善的地方有:

  • livingInfo 数组和 anchorInfo 数组可以通过 computed 属性计算合成一个大的数组,那么很多的过滤器还有 forEach 遍历就可以省略掉了

  • 可以把整个 ul 下的部分做成一个组件

  • 文章可能描述的很啰嗦

全部代码:

源码地址

guide.html:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>vue guide</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
  7. <meta content="telephone=no" name="format-detection" />
  8. <meta content="email=no" name="format-detection" />
  9. <link rel="stylesheet" href="./css/guide.css" />
  10. <script src="http://7xnv74.com1.z0.glb.clouddn.com/static/lib/flexible/flexible.js"></script>
  11. </head>
  12. <body>
  13. <div class="container" id="app">
  14. <div class="radio-wrapper">
  15. <ul class="list clearfix" v-cloak>
  16. <li v-for="anchor in anchorInfo">
  17. <a class="link" @click="jumpVideo(anchor)">
  18. <div class="live" v-show="living | getLiving anchor" @click.stop="jumpLive(anchor)">
  19. <p>观看直播 ></p>
  20. </div>
  21. <img :src="anchor.userID | getUserImg" class="user">
  22. <img src="./images/play.png" class="play">
  23. <p class="add" v-show="anchor.showAdd">+1</p>
  24. </a>
  25. <div class="user-wrapper">
  26. <div class="name" v-text="anchor.anchorName" @click="jumpProfile(anchor.userID)"></div>
  27. <div class="num" v-text="anchor.supportCnt"></div>
  28. </div>
  29. <template v-if="voteStatus | getVoteStatus anchor">
  30. <div class="had-btn">
  31. <p>今日已支持</p>
  32. </div>
  33. </template>
  34. <template v-else>
  35. <div class="do-btn" @click="singerVote(anchor)">
  36. <p>支持</p>
  37. </div>
  38. </template>
  39. </li>
  40. </ul>
  41. </div>
  42. </div>
  43. <script src="http://7xnv74.com1.z0.glb.clouddn.com/static/lib/fastclick/fastclick.min.js"></script>
  44. <script src="./js/vue.min.js"></script>
  45. <script src="./js/vue-resource.min.js"></script>
  46. <script src="./js/guide.js"></script>
  47. </body>
  48. </html>

guide.js

  1. var lib = {
  2. urlParams: function(url) {
  3. var urlParamsList = {};
  4. var params = url.search.replace(/^\?/, "").split('&'); //分开成各个不同的对像,去掉'&'
  5. for (var i = 0; i < params.length; i++) {
  6. var param = params[i];
  7. var temp = param.split("=");
  8. urlParamsList[temp[0]] = decodeURI(temp[1]);
  9. }
  10. return urlParamsList;
  11. }
  12. };
  13. window.onload = function() {
  14. var attachFastClick = Origami.fastclick;
  15. attachFastClick(document.body);
  16. var windowLocation = window.location,
  17. selfUserID = lib.urlParams(windowLocation)['userID'],
  18. selfSessionID = lib.urlParams(windowLocation)['sessionID'],
  19. selfSessionToken = lib.urlParams(windowLocation)['sessionToken'],
  20. selfPeerID = lib.urlParams(windowLocation)['peerID'];
  21. var app = new Vue({
  22. el: '#app',
  23. data: {
  24. anchorInfo: [],
  25. livingInfo: [],
  26. getAnchorInfoUrl: "http://a.impingo.me/activity/getAnchorInfo",
  27. getLiveStatusUrl: "http://a.impingo.me/activity/getLiveStatus",
  28. queryVoteStatusUrl: "http://a.impingo.me/activity/queryVoteStatus",
  29. singerVoteUrl: "http://a.impingo.me/activity/singerVote",
  30. anchorUserID: '',
  31. todayHadVote: false,
  32. setIntervalGetLiveStatus: null,
  33. setIntervalGetAnchorInfo: null,
  34. intervalDuration: 60 * 1000,
  35. },
  36. ready: function() {
  37. this.getAnchorInfo();
  38. this.getLiveStatus();
  39. this.queryVoteStatus();
  40. this.initSetTimeout();
  41. },
  42. methods: {
  43. getAnchorInfo: function() {
  44. this.$http.jsonp(this.getAnchorInfoUrl)
  45. .then(function(res) {
  46. console.log(res);
  47. var rtnData = res.data;
  48. if (rtnData.rtn == 0) {
  49. this.$set('anchorInfo', rtnData.data);
  50. }
  51. })
  52. .catch(function(res) {
  53. console.info('网络失败');
  54. });
  55. },
  56. getLiveStatus: function() {
  57. this.$http.jsonp(this.getLiveStatusUrl)
  58. .then(function(res) {
  59. var that = this;
  60. var rtnData = res.data;
  61. if (rtnData.rtn == 0) {
  62. this.$set('livingInfo', rtnData.data);
  63. }
  64. })
  65. .catch(function(res) {
  66. console.info('网络失败');
  67. });
  68. },
  69. queryVoteStatus: function() {
  70. // this.$http.jsonp(this.queryVoteStatusUrl + '?userID=' + selfUserID)
  71. this.$http.jsonp(this.queryVoteStatusUrl, {
  72. params: {
  73. 'userID': selfUserID
  74. }
  75. })
  76. .then(function(res) {
  77. var rtnData = res.data;
  78. if (rtnData.rtn == 0) {
  79. this.todayHadVote = false;
  80. } else if (rtnData.rtn == 1) {
  81. this.todayHadVote = true;
  82. this.anchorUserID = rtnData.data.anchorUserID;
  83. }
  84. })
  85. .catch(function(res) {
  86. console.info('网络失败');
  87. });
  88. },
  89. initSetTimeout: function() {
  90. var that = this;
  91. setIntervalGetAnchorInfo = setInterval(function() {
  92. that.getAnchorInfo();
  93. }, that.intervalDuration);
  94. setIntervalGetLiveStatus = setInterval(function() {
  95. that.getLiveStatus();
  96. }, that.intervalDuration);
  97. },
  98. singerVote: function(anchor) {
  99. var getUserID = selfUserID,
  100. getTargetUserID = anchor.userID;
  101. if (this.todayHadVote) {
  102. console.info('每日仅支持一次!');
  103. return;
  104. }
  105. this.$http.jsonp(this.singerVoteUrl, {
  106. params: {
  107. userID: getUserID,
  108. targetUserID: getTargetUserID,
  109. sessionID: selfSessionID,
  110. sessionToken: selfSessionToken,
  111. peerID: selfPeerID
  112. }
  113. })
  114. .then(function(res) {
  115. var rtnData = res.data,
  116. that = this;
  117. if (rtnData.rtn == 0) {
  118. // console.info(rtnData.msg);
  119. Vue.set(anchor, 'showAdd', true);
  120. anchor.supportCnt++;
  121. this.anchorUserID = getTargetUserID;
  122. this.todayHadVote = true;
  123. clearInterval(setIntervalGetAnchorInfo);
  124. // 点击投票,动画(2秒)以后,重新拉取直播状态以及直播信息
  125. setTimeout(function() {
  126. that.getAnchorInfo();
  127. that.getLiveStatus();
  128. setIntervalGetAnchorInfo = setInterval(function() {
  129. that.getAnchorInfo();
  130. }, that.intervalDuration);
  131. }, 2000);
  132. } else if (rtnData.rtn == 2 || rtnData.rtn == 3 || rtnData.rtn == 1) {
  133. console.info(rtnData.msg);
  134. }
  135. })
  136. .catch(function(res) {
  137. console.info('网络失败');
  138. });
  139. },
  140. jumpProfile: function(userID) {
  141. console.log(userID);
  142. if (window.pingo_js) {
  143. window.pingo_js.jumpPage('profile://' + userID);
  144. }
  145. },
  146. jumpVideo: function(anchor) {
  147. var curUserID = anchor.userID;
  148. window.location.href = 'http://api.impingo.me/static/singer/preselection-live.html?userID=' + curUserID; // 视频地址
  149. return;
  150. },
  151. jumpLive: function(anchor) {
  152. var curUserID = anchor.userID,
  153. curRoomID;
  154. this.livingInfo.forEach(function(living) {
  155. if (living.createUserID === curUserID) {
  156. if (living.state == "1") {
  157. curRoomID = living.roomID;
  158. }
  159. }
  160. });
  161. window.location.href = 'http://api.impingo.me/miniSite/livePage?liveID=' + curRoomID;
  162. }
  163. },
  164. filters: {
  165. getUserImg: function(val) {
  166. return 'http://a.impingo.me/static/activity/singer/resource/' + val + '.jpg'
  167. },
  168. getLiving: function(val, anchor) {
  169. var curUserID = anchor.userID,
  170. isLiving = false;
  171. this.livingInfo.forEach(function(living) {
  172. if (living.createUserID === curUserID) {
  173. if (living.state == "1") {
  174. isLiving = true;
  175. }
  176. }
  177. });
  178. return isLiving;
  179. },
  180. getVoteStatus: function(val, anchor) {
  181. if (anchor.userID == this.anchorUserID) {
  182. // 可支持
  183. return true;
  184. } else {
  185. // 不可支持
  186. return false;
  187. }
  188. },
  189. },
  190. });
  191. }

guide.less

  1. @import (inline) './normalize.css';
  2. body {
  3. background-color: #010017;
  4. }
  5. .container {
  6. user-select: none;
  7. font-family: 'Microsoft YaHei', sans-serif;
  8. position: relative;
  9. min-width: 320px;
  10. max-width: 750px;
  11. margin: 0 auto;
  12. font-size: 0.32rem;
  13. }
  14. [v-cloak] {
  15. display: none;
  16. }
  17. // 设计稿是 750px
  18. // 1rem = 75px
  19. @base: 75rem;
  20. .demo {
  21. text-align: center;
  22. .btn {
  23. width: 560 / @base;
  24. }
  25. }

 

一个简单的 vue.js 实践教程的更多相关文章

  1. 一个简单的Vue.js组件开发示例

    //创建属于自己的vue组件库 (function(Vue, undefined) { Vue.component("my-component", { template: '< ...

  2. 搭建Vue.js环境,建立一个简单的Vue项目

    基于vue-cli快速构建 Vue是近年来比较火的一个前端框架,所以搭建Vue.js环境,要装webpack,vue-cli,Vue 安装webpack命令如下 $ cnpm install webp ...

  3. Vue.js入学教程

    Vue.js是什么Vue.js 是用于构建交互式的 Web 界面的库.Vue.js 提供了 MVVM 数据绑定和一个可组合的组件系统,具有简单.灵活的 API.Vue.js(类似于view)是一套构建 ...

  4. [转]Vue.js 入门教程

    本文转自:http://www.runoob.com/w3cnote/vue-js-quickstart.html 什么是 Vue.js? Vue.js 是用于构建交互式的 Web  界面的库. Vu ...

  5. 手把手教你从零写一个简单的 VUE

    本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...

  6. Vue.js 入门教程

    Vue.js 入门教程:https://cn.vuejs.org/v2/guide/index.html

  7. Vue.js 系列教程 ②

    这是关于 JavaScript 框架 Vue.js 五个教程的第二部分.在这一部分,我们将学习组件,Props 以及 Slots.这不是一个完整的指南,而是基础知识的概述,所以你可以了解Vue.js ...

  8. Vue.js 系列教程 3:Vue-cli,生命周期钩子

    原文:intro-to-vue-3-vue-cli-lifecycle-hooks 译者:nzbin 这是 JavaScript 框架 Vue.js 五篇教程的第三部分.在这一部分,我们将学习 Vue ...

  9. Vue.js 系列教程 4:Vuex

    这是关于 JavaScript 框架 Vue.js 五个教程的第四部分.在这一部分,我们会学习使用 Vuex 进行状态管理. 这不是一个完整的指南,而是基础知识的概述,所以你可以了解 Vue.js 以 ...

随机推荐

  1. [C++]猜数字游戏的提示(Master-Mind Hints,UVa340)

    [本博文非博主原创,思路与题目均摘自 刘汝佳<算法竞赛与入门经典(第2版)>] Question 例题3-4 猜数字游戏的提示(Master-Mind Hints,UVa340) 实现一个 ...

  2. PHP:产生不反复随机数的方法

    来源:http://www.ido321.com/1217.html 不管是Web应用,还是WAP或者移动应用,随机数都有其用武之地.在近期接触的几个小项目中.我也经常须要和随机数或者随机数组打交道, ...

  3. 第26月第18天 mybatis_spring_mvc

    1. applicationContext.xml  配置文件里最主要的配置: <?xml version="1.0" encoding="utf-8"? ...

  4. 向dnsrecord.txt 中添加 配置

    #!/bin/bash Ip_addr=`cat /etc/dnsrecord.txt |awk '{print $1}' |head -1` Check_dns_url(){ grep $1 /et ...

  5. xtrabackup 在线主从搭建

    因为意外导致某个MySQL的从服务器宕机,且不可修复,因为是业务数据库,不能停机和锁表进行从库的搭建,所以考虑了使用xtrabackup 进行在线主从搭建. 一.数据库环境 注意:  主从搭建主库一定 ...

  6. 5.21http网页基础

    1,HTML的由来: web网页开发的标准,由w3c万维网联盟组织制定的.是制作网页的规范标准,分为结构标准.表现标准.行为标准.结构:html.表现:css.行为:Javascript. 2,htm ...

  7. Axure8破解码

    升级了 8.0.0.3321 版本后,原来的 license 失效了,解决方法就是使用下面的这组注册码 License:米 业成 (STUDENT)Key:nFmqBBvEqdvbiUjy8NZiyW ...

  8. 【提示框】【计时事件】【cookie】

    1.提示框 1)警告框 <script>function disp_alert(){alert("我是警告框!!")}</script> 2)确认框 fun ...

  9. MGR架构 ~ 节点的维护相关问题

    一简介:MGR节点的相关维护二 两种情况   1 MGR读节点异常停止,然后重新启动加入节点进行数据同步   2 MGR读节点新加入集群成员,启动复制进行数据同步三 通用过程  1 新加入节点通过通道 ...

  10. 一张图片资源要占用多大内存xhdpi xxhdpi

    一张图片资源要占用多大内存,可以用下面的计算公式计算 4 * withPixel*(targetDensity /sourcedensity) * heightPixel*(targetDensity ...