Vue.js技术栈搭建CNODE社区

关键词:

Vue.js文档、Promise、jquery、webpack、npm、vue-­cli、vue-router

一 、cnode社区的基本构架

组件:
Header 头部
PosltList 列表
Article 文章的详情页
SlideBar 侧边栏
UserInfo 用户个人信息
Psgination 分页组件

二 、从头开始

我们先做出大概效果,接收和反馈数据数据

2.1 Header组件

Header组件

首先搭建好vue-­cli,进入编辑器

  • 将demo中要用到的图片和logo都存入assets文件夹中,如图
  • 创建一个Header组件,用img标签引入存储在assets文件中的logo
  • 在li标签分别写出:新手入门、API、关于、注册、登录
  • 将组件引入App.vue,就可以在页面显示
<template>
  <div id="app">
    <Header></Header>
  </div>
</template>

<script>
import Header from './components/header.vue'
export default {
  name: 'App',
  components:{
    Header
  }
}
  • 在加入css,第一个组件基本完成

2.2 PostList组件

PostList组件
  • 在数据还未到来时,我们显示出一个正在加载的logo,用img标签引入一个动态图片,用v-if操作这个动态图的显示
<div class="loading" v-if="isLoading">
         <img src="../assets/loading.gif" alt="">
</div>
  • 在组件data中定义一个空数组,并且让加载logo默认不显示
data(){
      return{
          isLoading: false,
          posts:[]
      }
  }
  • 获取数据用到Axios中的get请求,引入之后将Axios全局挂载到Vue原型上,通过this.$http来对网页进行请求
import Axios from 'axios'
Vue.prototype.$http = Axios;
  • 在methods方法中定义一个方法getData,通过get方法获取数据,通过promise返回请求成功和请求失败的数据,并做对应的事
methods:{
      getData(){
          this.$http.get('https://cnodejs.org/api/v1/topics',{
            params:{
              page:this.postPage,
              limit:20
            }
          })
          .then(res=>{
              this.isLoading = false  //加载成功,去除动画
              this.posts = res.data.data
              // console.log(this.posts)
          })
          .catch(err=>{
            //处理返回失败后的问题
            console.log(err) 
          })
      }
  • 请求成功后给data中定义的posts空数组赋值,如上代码所示

  • 在页面加载之前,触发getData方法,并且在数据没有到来之前,显示加载logo,如代码所示

beforeMount(){
      this.isLoading = true //加载成功之前显示加载动画
      this.getData() //在页面加载之前获取数据
  }
  • 得到数据后,渲染页面用v-for,根据数据结构写出所需要的数据
<div class="posts" v-else>
         <ul>
             <li>
                 <div class="toobar">
                     <span v-for="tab in change_tab" :key="tab.name"
                     @click="changetab(tab)"
                     :class="{tab_style:tab == current_tab}">{{tab}}</span>
                 </div>
             </li>
             <li v-for="post in posts" :key ="post.id">
                 <!--头像-->
                 <router-link :to="{
                   name:'user_info',
                   params:{name:post.author.loginname}
                 }">
                   <img :src="post.author.avatar_url" alt="">
                 </router-link>
                 <!--回复/浏览-->
                 <span class="allcount">
                     <span class="reply_count">{{post.reply_count}}</span>
                     /{{post.visit_count}}
                 </span>
                 <!--帖子的分类-->
                 <span :class="[{put_good:(post.good  == true),put_top:(post.top  == true),
                 'topiclist-tab':(post.good != true && post.top != true)}]">
                   <span>
                     {{post | tabFormatter}}
                   </span>
                </span>
                 <!--标题-->
                 <router-link :to="{
                   name:'post_content',
                   params:{
                     id:post.id,
                     name:post.author.loginname
                   }
                 }">
                   <span>{{post.title}}</span>
                 </router-link>
                 <!--最終回复时间-->
                 <span class="last_reply">{{post.last_reply_at | formatDate}}</span>
             </li>
  • 页面中有帖子分类,所以这里要用到过滤器,我们将过滤器代码写入main.js中以便于各个组件使用
Vue.filter('tabFormatter',function (post) {
  if(post.good == true){
    return '精华'
  }else if(post.top == true){
    return '置顶'
  }else if(post.tab == 'ask'){
    return '问答'
  }else if(post.tab == 'share'){
    return '分享'
  }else{
    return '招聘'
  }
})
  • 通过v-bind动态绑定class控制分类的样式

  • 因为头像和标题都会跳转,所以用router-link来包裹,通过to来跳转到需要的路由和要传统的所需参数

<!--头像-->
                 <router-link :to="{
                   name:'user_info',
                   params:{name:post.author.loginname}
                 }">
                   <img :src="post.author.avatar_url" alt="">
                 </router-link>
<!--标题-->
                 <router-link :to="{
                   name:'post_content',
                   params:{
                     id:post.id,
                     name:post.author.loginname
                   }
                 }">
                   <span>{{post.title}}</span>
                 </router-link>
  • 在roouter文件夹下的index.js文件中引入vue-router,然后定义路由名字,接收传递来的参数,引入其他组件,代码如下:
import Vue from 'vue'
import Router from 'vue-router'
import PostList from '../components/PostList'
import Article from '../components/Article'
import SlideBar from '../components/SlideBar'
Vue.use(Router)

export default new Router({
  routes: [
    {
      name:'root',
      path:'/',
      components:{
        main:PostList
      }
    },
    {
      name:'post_content',
      path:'/topic/:id&author=:name',
      components:{
        main:Article,
        slidebar:SlideBar
      }
    }
  ]
})
  • 并且还要在父组件中引入router
import Vue from 'vue'
import App from './App'
import router from './router'
import Axios from 'axios'
Vue.prototype.$http = Axios;

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})
  • 最后在App.vue组件中由router-view组件来接收,通过name的值来判定需要显示的组件
<template>
  <div id="app">
    <Header></Header>
    <div class="main">
      <router-view name="slidebar"></router-view>
      <router-view name="main"></router-view>
    </div>
  </div>
</template>

<script>
import Header from './components/header.vue'
// import PostList from './components/PostList.vue'
// import UserInfo from './components/UserInfo.vue'
export default {
  name: 'App',
  components:{
    Header
  }
}

2.3 大同小异

UserInfo组件:

UserInfo组件

SlideBar组件:
SlideBar组件

Article组件:
上部分
Article组件上部分

下部分
Article组件下部分

后面的Article组件、SlideBar组件、UserInfo组件写法都是一样的,有跳转的用router.link包裹,传递路由名字和参数,遇到需要遍历的就用v-for遍历,需要注意的有两个地方,如下

  • 第一个是在Article父组件中,因为写完后会发现点击下图中两个区域(SlideBar组件)页面没有变化,路由的参数虽然变了,但并不会跳转。所以我们要在Article组件中用watch监听路由的变化以便进行跳转


    SlideBar组件

注意看api参数:

第一个

第二个

监听路由代码:

watch:{
    '$route'(to,from){
      this.getArticleData()
    }
  }
  • 第二个是在SlideBar组件中,因为只需要获取五条数据,所以要在计算属性中写两个方法,然后在模板中用v-for来渲染,例:v-for="list in topcilimitby5"
computed:{
        topcilimitby5(){
          if(this.userinfo.recent_topics){
            return this.userinfo.recent_topics.slice(0,5);
          }
        },
        replylimitby5(){
          if(this.userinfo.recent_replies){
            return this.userinfo.recent_replies.slice(0,5);
          }
        }
      }

2.4 Psgination组件

Psgination组件

直接甩代码更明了:

<template>
<div class="pagination">
  <button @click="changeBtn">首页</button>
  <button @click="changeBtn">上一页</button>
  <button v-if="jduge" class="pagebtn">......</button>
  <button v-for="btn in pagebtns" :key="btn.page"
  :class="[{currentPage:btn == currentPage},'pagebtn']"
  @click="changeBtn(btn)">
      {{btn}}
  </button>
  <button @click="changeBtn">下一页</button>
</div>
</template>

<script>
import $ from 'jquery'
export default {
    name:'Pagination',
    data(){
        return{
            pagebtns:[1,2,3,4,5,'......'],
            currentPage:1,
            jduge:false
        }
    },
    methods:{
        changeBtn(page){
            // console.log(page.target)
            if(page == '......') return
          if(typeof page != 'number'){
            switch(page.target.innerText){
              case '上一页':
                $('button.currentPage').prev().click();
                // console.log( $('button.currentPage'))
                break;
              case '下一页':
                $('button.currentPage').next().click();
                break;
              case '首页':
                this.pagebtns = [1,2,3,4,5,'......'];
                this.changeBtn(1);
                break;
              default:
                break;
            }
            return;
          }
            this.currentPage = page
            if(page>4){
              this.jduge = true;
            }else{
              this.jduge = false;
            }
            if(page == this.pagebtns[4]){
                this.pagebtns.shift()
                this.pagebtns.splice(4,0,this.pagebtns[3]+1)
            }else if(page == this.pagebtns[0] && page != 1){
                this.pagebtns.unshift(this.pagebtns[0]-1)
                this.pagebtns.splice(5,1)
            }
            this.$emit('handleList',this.currentPage);
        }
    }
}
</script>
  • 在data中定义pagebtns,如:pagebtns:[1,2,3,4,5,'......'],在模板中由v-for来遍历,然后在定义个changBtn方法,点击时实现需要的功能,此段需要引入jquery,用switch判定点击上一页、下一页、首页该做什么事

  • 最后,因为Psgination组件是定义在PostList组件中,所以是子组件向父组件传递数据,所以这里需要用到,自定义事件,用Vue中的方法this.$emit('handleList',this.currentPage)
    再在PostList组件中定义出handleList方法即可,在给出我的PostList组件最后的代码:

<template>
 <div class="PostList">
     <!--在数据未返回的时候,显示这个正在加载的gif-->
     <div class="loading" v-if="isLoading">
         <img src="../assets/loading.gif" alt="">
     </div>
     <div class="posts" v-else>
         <ul>
             <li>
                 <div class="toobar">
                     <span v-for="tab in change_tab" :key="tab.name"
                     @click="changetab(tab)"
                     :class="{tab_style:tab == current_tab}">{{tab}}</span>
                 </div>
             </li>
             <li v-for="post in posts" :key ="post.id">
                 <!--头像-->
                 <router-link :to="{
                   name:'user_info',
                   params:{name:post.author.loginname}
                 }">
                   <img :src="post.author.avatar_url" alt="">
                 </router-link>
                 <!--回复/浏览-->
                 <span class="allcount">
                     <span class="reply_count">{{post.reply_count}}</span>
                     /{{post.visit_count}}
                 </span>
                 <!--帖子的分类-->
                 <span :class="[{put_good:(post.good  == true),put_top:(post.top  == true),
                 'topiclist-tab':(post.good != true && post.top != true)}]">
                   <span>
                     {{post | tabFormatter}}
                   </span>
                </span>
                 <!--标题-->
                 <router-link :to="{
                   name:'post_content',
                   params:{
                     id:post.id,
                     name:post.author.loginname
                   }
                 }">
                   <span>{{post.title}}</span>
                 </router-link>
                 <!--最終回复时间-->
                 <span class="last_reply">{{post.last_reply_at | formatDate}}</span>
             </li>
             <li>
               <Pagination @handleList="renderList"></Pagination>
             </li>
         </ul>
     </div>
 </div>
</template>

<script>
import Pagination from './Pagination'
export default {
  name: 'PostList',
  data(){
      return{
          isLoading: false,
          posts:[],
          postPage:1,
          change_tab:['全部','精华','分享','问答','招聘'],
          current_tab:'全部',
          url:'https://cnodejs.org/api/v1/topics'
      }
  },
  components:{
    Pagination
  },
  methods:{
      getData(){
          this.$http.get(this.url,{
            params:{
              page:this.postPage,
              limit:20
            }
          })
          .then(res=>{
              this.isLoading = false  //加载成功,去除动画
              this.posts = res.data.data
              // console.log(this.posts)
          })
          .catch(err=>{
            //处理返回失败后的问题
            console.log(err) 
          })
      },
      renderList(value){
        this.postPage =value
        this.getData()
      },
    changetab(tab){
      if(tab == '全部'){
        console.log(this.url)
        this.url = 'https://cnodejs.org/api/v1/topics?tab=all'
      }else if(tab == '精华'){
        console.log(this.url)
        this.url = 'https://cnodejs.org/api/v1/topics?tab=good'
      }else if(tab == '分享'){
        console.log(this.url)
        this.url = 'https://cnodejs.org/api/v1/topics?tab=share'
      }else if(tab == '问答'){
        console.log(this.url)
        this.url = 'https://cnodejs.org/api/v1/topics?tab=ask'
      }else{
        this.url = 'https://cnodejs.org/api/v1/topics?tab=job'
      }
      this.current_tab = tab
      this.getData()
    }
  },
  beforeMount(){
      this.isLoading = true //加载成功之前显示加载动画
      this.getData() //在页面加载之前获取数据
  }
}
</script>

三、大结局

写到这里,模拟cnode社区基本写完了,后面便是一些自己想要的功能,可以自行添加-----------The early bird catches the worm

推荐阅读更多精彩内容

  • # 传智播客vue 学习## 1. 什么是 Vue.js* Vue 开发手机 APP 需要借助于 Weex* Vu...
    再见天才阅读 1,620评论 0 6
  • 相关概念 混合开发和前后端分离 混合开发(服务器端渲染) 前后端分离后端提供接口,前端开发界面效果(专注于用户的交...
    他爱在黑暗中漫游阅读 1,573评论 4 46
  • 教诲的甘露第三诗节:有六项原则,如果我们遵守,有利于我们的奉献服务; 1.热情; 2.信心; 3.恒心; 4.遵守...
    lovekrishina阅读 110评论 0 0
  • Huiyingmua阅读 24评论 2 1
  • 所有人都称赞大雨之后的彩虹 却忽视大雪之后的星空 大概彩虹无论在哪里都会被聚焦 而星星 永远闪闪烁烁
    heim_dn阅读 16评论 0 1